503 lines
10 KiB
C
503 lines
10 KiB
C
|
/*
|
||
|
* sound/soc/codec/s5m3700x-dump.c
|
||
|
*
|
||
|
* ALSA SoC Audio Layer - Samsung Codec Driver
|
||
|
*
|
||
|
* Copyright (C) 2020 Samsung Electronics
|
||
|
*
|
||
|
* This program is free software; you can redistribute it and/or modify
|
||
|
* it under the terms of the GNU General Public License version 2 as
|
||
|
* published by the Free Software Foundation.
|
||
|
*/
|
||
|
|
||
|
#define pr_fmt(fmt) "codec_sysfs: " fmt
|
||
|
|
||
|
#include <linux/string.h>
|
||
|
#include <linux/module.h>
|
||
|
#include <linux/version.h>
|
||
|
#include <linux/kernel.h>
|
||
|
#include <linux/types.h>
|
||
|
#include <linux/kdev_t.h>
|
||
|
#include <linux/fs.h>
|
||
|
#include <linux/device.h>
|
||
|
#include <linux/cdev.h>
|
||
|
#include <linux/delay.h>
|
||
|
|
||
|
#include "s5m3700x.h"
|
||
|
|
||
|
#define SUCCESS 0
|
||
|
#define OK 1
|
||
|
#define FAIL -1
|
||
|
#define COMMAND_DUMP 1
|
||
|
#define COMMAND_READ 2
|
||
|
#define COMMAND_WRITE 3
|
||
|
#define MAX_STRING_LENGTH 64
|
||
|
#define ADDR_INDEX_STRING " 00 01 02 03 04 05 06 07 08 09 0a 0b 0c 0d 0e 0f\n"
|
||
|
|
||
|
char g_codec_command[MAX_STRING_LENGTH] = "dump";
|
||
|
char reg_dump[1000];
|
||
|
unsigned int g_command, g_arg1, g_arg2, g_arg3;
|
||
|
|
||
|
/*
|
||
|
* global variables for
|
||
|
* the codec_speedy_dev_t device number
|
||
|
* the character device structure
|
||
|
* the device class
|
||
|
*/
|
||
|
static dev_t codec_speedy_dev_t;
|
||
|
static struct cdev c_dev;
|
||
|
static struct class *class_codec_speedy;
|
||
|
|
||
|
static int codec_read_reg(unsigned short addr, u8 reg, u8 *dest)
|
||
|
{
|
||
|
int ret = 0;
|
||
|
|
||
|
ret = s5m3700x_i2c_read_reg(addr, reg, dest);
|
||
|
if (ret) {
|
||
|
pr_err("[%s] acpm ipc fail!\n", __func__);
|
||
|
return ret;
|
||
|
}
|
||
|
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
static int codec_write_reg(unsigned short addr, u8 reg, u8 value)
|
||
|
{
|
||
|
int ret = 0;
|
||
|
|
||
|
ret = s5m3700x_i2c_write_reg(addr, reg, value);
|
||
|
if (ret) {
|
||
|
pr_err("[%s] acpm ipc fail!\n", __func__);
|
||
|
return ret;
|
||
|
}
|
||
|
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
int atoh(char a)
|
||
|
{
|
||
|
if (a >= '0' && a <= '9')
|
||
|
return a - '0';
|
||
|
|
||
|
switch (a) {
|
||
|
case 'a': return 10;
|
||
|
case 'b': return 11;
|
||
|
case 'c': return 12;
|
||
|
case 'd': return 13;
|
||
|
case 'e': return 14;
|
||
|
case 'f': return 15;
|
||
|
case 'A': return 10;
|
||
|
case 'B': return 11;
|
||
|
case 'C': return 12;
|
||
|
case 'D': return 13;
|
||
|
case 'E': return 14;
|
||
|
case 'F': return 15;
|
||
|
default: return FAIL;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
int is_available_char(char a)
|
||
|
{
|
||
|
if (a >= '0' && a <= '9')
|
||
|
return OK;
|
||
|
if (a >= 'a' && a <= 'f')
|
||
|
return OK;
|
||
|
if (a >= 'A' && a <= 'F')
|
||
|
return OK;
|
||
|
if (a == 'x' || a == 'X')
|
||
|
return OK;
|
||
|
|
||
|
return FAIL;
|
||
|
}
|
||
|
|
||
|
int str2hex(char *str)
|
||
|
{
|
||
|
int i = 0, value = 0;
|
||
|
unsigned int temp_arg1 = 0, temp_arg2 = 0;
|
||
|
|
||
|
/*
|
||
|
* the max strnlen is 4 for the 0x00
|
||
|
* but the format could accept like 0, 00, 0x0
|
||
|
*/
|
||
|
for (i = 0; i < 5; i++)
|
||
|
if ((is_available_char(*(str+i)) == FAIL)
|
||
|
&& (*(str+i) != 'x')
|
||
|
&& (*(str+i) != 'X'))
|
||
|
break;
|
||
|
|
||
|
if (i <= 0 || i > 4) {
|
||
|
/* return fail with exceptional cases */
|
||
|
pr_err("sysfs: wrong arguments\n");
|
||
|
return FAIL;
|
||
|
} else if (i == 1) {
|
||
|
/* in case of the one char hex format
|
||
|
* like 0, 1, 2 ... e and f
|
||
|
*/
|
||
|
value = atoh(str[0]);
|
||
|
if (value == FAIL)
|
||
|
return FAIL;
|
||
|
} else if (i == 2) {
|
||
|
/* in case of the 2 chars hex format
|
||
|
* like 00, 01, 02 ... 0e and 0f
|
||
|
*/
|
||
|
temp_arg1 = atoh(str[0]);
|
||
|
temp_arg2 = atoh(str[1]);
|
||
|
|
||
|
if ((temp_arg1 == FAIL) || (temp_arg2 == FAIL)) {
|
||
|
pr_err("sysfs: temp_args = FAIL.\n");
|
||
|
return FAIL;
|
||
|
}
|
||
|
value = temp_arg1 * 16 + temp_arg2;
|
||
|
} else if (i == 3 && (str[1] == 'x')) {
|
||
|
value = atoh(str[2]);
|
||
|
} else if (i == 4 && (str[1] == 'x')) {
|
||
|
/* in case of the 4 chars hex format
|
||
|
* like 0x00, 0x01, 0x02 ... 0x0e and 0x0f
|
||
|
*/
|
||
|
temp_arg1 = atoh(str[2]);
|
||
|
temp_arg2 = atoh(str[3]);
|
||
|
|
||
|
if ((temp_arg1 == FAIL) || (temp_arg2 == FAIL)) {
|
||
|
pr_err("sysfs: temp_args = FAIL.\n");
|
||
|
return FAIL;
|
||
|
}
|
||
|
value = temp_arg1 * 16 + temp_arg2;
|
||
|
} else {
|
||
|
pr_err("sysfs: out of length\n");
|
||
|
return FAIL;
|
||
|
}
|
||
|
|
||
|
return value;
|
||
|
}
|
||
|
|
||
|
int command_parsing(char *s1)
|
||
|
{
|
||
|
unsigned int i = 0;
|
||
|
const char *delimit = " ";
|
||
|
char arg1[8], arg2[8], arg3[8];
|
||
|
|
||
|
/* Skip leading delimiters if new string. */
|
||
|
if (s1 == NULL)
|
||
|
return FAIL;
|
||
|
s1 += strspn(s1, delimit);
|
||
|
|
||
|
memset(arg1, 0, sizeof(arg1));
|
||
|
memset(arg2, 0, sizeof(arg2));
|
||
|
memset(arg3, 0, sizeof(arg3));
|
||
|
memset(g_codec_command, 0, sizeof(g_codec_command));
|
||
|
|
||
|
/* extract a command*/
|
||
|
if (!strncmp(s1, "dump", strlen("dump"))) {
|
||
|
strncpy(g_codec_command, s1, strlen("dump"));
|
||
|
g_command = COMMAND_DUMP;
|
||
|
s1 = s1 + strlen("dump");
|
||
|
} else if (!strncmp(s1, "read", strlen("read"))) {
|
||
|
strncpy(g_codec_command, s1, strlen("read"));
|
||
|
g_command = COMMAND_READ;
|
||
|
s1 = s1 + strlen("read");
|
||
|
} else if (!strncmp(s1, "write", strlen("write"))) {
|
||
|
strncpy(g_codec_command, s1, strlen("write"));
|
||
|
g_command = COMMAND_WRITE;
|
||
|
s1 = s1 + strlen("write");
|
||
|
} else
|
||
|
return FAIL;
|
||
|
|
||
|
/* check if no more string */
|
||
|
while (*s1 == ' ')
|
||
|
s1++;
|
||
|
if (*s1 == '\0')
|
||
|
return FAIL;
|
||
|
|
||
|
/* extracting first arg (slave address) */
|
||
|
i = 0;
|
||
|
while ((*s1 != ' ') && (*s1 != '\0') && is_available_char(*s1) != FAIL) {
|
||
|
arg1[i] = *s1;
|
||
|
i++;
|
||
|
s1++;
|
||
|
if (i >= 4)
|
||
|
return FAIL;
|
||
|
}
|
||
|
arg1[i] = '\0';
|
||
|
|
||
|
/* set slave address and return after have 1 arg */
|
||
|
g_arg1 = str2hex(arg1);
|
||
|
if (g_arg1 == FAIL)
|
||
|
return FAIL;
|
||
|
if (g_command == COMMAND_DUMP)
|
||
|
return SUCCESS;
|
||
|
|
||
|
/* check if no more string */
|
||
|
while (*s1 == ' ')
|
||
|
s1++;
|
||
|
if (*s1 == '\0')
|
||
|
return FAIL;
|
||
|
|
||
|
/* extracting second arg (register address) */
|
||
|
i = 0;
|
||
|
while ((*s1 != ' ') && (*s1 != '\0') && is_available_char(*s1) != FAIL) {
|
||
|
arg2[i] = *s1;
|
||
|
i++;
|
||
|
s1++;
|
||
|
if (i >= 8)
|
||
|
return FAIL;
|
||
|
}
|
||
|
arg2[i] = '\0';
|
||
|
|
||
|
/* set register address and return after have 2 arg */
|
||
|
g_arg2 = str2hex(arg2);
|
||
|
if (g_arg2 == FAIL)
|
||
|
return FAIL;
|
||
|
if (g_command == COMMAND_READ)
|
||
|
return SUCCESS;
|
||
|
|
||
|
/* check if no more string */
|
||
|
while (*s1 == ' ')
|
||
|
s1++;
|
||
|
if (*s1 == '\0')
|
||
|
return FAIL;
|
||
|
|
||
|
/* extracting third arg (register value) */
|
||
|
i = 0;
|
||
|
while ((*s1 != ' ') && (*s1 != '\0') && is_available_char(*s1) != FAIL) {
|
||
|
arg3[i] = *s1;
|
||
|
i++;
|
||
|
s1++;
|
||
|
if (i >= 8)
|
||
|
return FAIL;
|
||
|
}
|
||
|
arg3[i] = '\0';
|
||
|
|
||
|
/* set register address and return after have 3 arg */
|
||
|
g_arg3 = str2hex(arg3);
|
||
|
if (g_arg3 == FAIL)
|
||
|
return FAIL;
|
||
|
|
||
|
return SUCCESS;
|
||
|
}
|
||
|
|
||
|
static int codec_speedy_open(struct inode *i, struct file *f)
|
||
|
{
|
||
|
pr_info("Driver: open()\n");
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
static int codec_speedy_close(struct inode *i, struct file *f)
|
||
|
{
|
||
|
pr_info("Driver: close()\n");
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
void value2hex(unsigned int value, char *temp)
|
||
|
{
|
||
|
unsigned int first, second;
|
||
|
|
||
|
first = (value & 0x00F0) / 16;
|
||
|
second = value & 0x000F;
|
||
|
switch (first) {
|
||
|
case 0xa:
|
||
|
*temp = 'a';
|
||
|
break;
|
||
|
case 0xb:
|
||
|
*temp = 'b';
|
||
|
break;
|
||
|
case 0xc:
|
||
|
*temp = 'c';
|
||
|
break;
|
||
|
case 0xd:
|
||
|
*temp = 'd';
|
||
|
break;
|
||
|
case 0xe:
|
||
|
*temp = 'e';
|
||
|
break;
|
||
|
case 0xf:
|
||
|
*temp = 'f';
|
||
|
break;
|
||
|
default:
|
||
|
*temp = first + '0';
|
||
|
break;
|
||
|
}
|
||
|
|
||
|
switch (second) {
|
||
|
case 0xa:
|
||
|
*(temp+1) = 'a';
|
||
|
break;
|
||
|
case 0xb:
|
||
|
*(temp+1) = 'b';
|
||
|
break;
|
||
|
case 0xc:
|
||
|
*(temp+1) = 'c';
|
||
|
break;
|
||
|
case 0xd:
|
||
|
*(temp+1) = 'd';
|
||
|
break;
|
||
|
case 0xe:
|
||
|
*(temp+1) = 'e';
|
||
|
break;
|
||
|
case 0xf:
|
||
|
*(temp+1) = 'f';
|
||
|
break;
|
||
|
default:
|
||
|
*(temp+1) = second + '0';
|
||
|
break;
|
||
|
}
|
||
|
|
||
|
*(temp+2) = '\0';
|
||
|
}
|
||
|
|
||
|
static ssize_t codec_speedy_read(struct file *f,
|
||
|
char __user *buf,
|
||
|
size_t len,
|
||
|
loff_t *off)
|
||
|
{
|
||
|
unsigned int value, i;
|
||
|
char temp[3];
|
||
|
|
||
|
pr_info("sysfs driver: read()\n");
|
||
|
|
||
|
memset(reg_dump, 0, sizeof(reg_dump));
|
||
|
if (g_command == COMMAND_DUMP) {
|
||
|
|
||
|
strncpy(reg_dump,
|
||
|
ADDR_INDEX_STRING,
|
||
|
sizeof(ADDR_INDEX_STRING));
|
||
|
|
||
|
/* register addresses 0x00 ~ 0xFF(255) */
|
||
|
for (i = 0; i <= 255; i++) {
|
||
|
/* line numbers like 0x10, 0x20, 0x30 ... 0xff
|
||
|
* in the first colume
|
||
|
*/
|
||
|
if (i % 16 == 0) {
|
||
|
value2hex(i, temp);
|
||
|
strcat(reg_dump, temp);
|
||
|
strcat(reg_dump, ":");
|
||
|
}
|
||
|
|
||
|
codec_read_reg(g_arg1, i, (u8 *)&value);
|
||
|
|
||
|
strcat(reg_dump, " ");
|
||
|
value2hex(value, temp);
|
||
|
strcat(reg_dump, temp);
|
||
|
|
||
|
if (i % 16 == 15)
|
||
|
strcat(reg_dump, "\n");
|
||
|
}
|
||
|
} else if (g_command == COMMAND_READ) {
|
||
|
codec_read_reg(g_arg1, g_arg2, (u8 *)&value);
|
||
|
g_arg3 = (unsigned int)value & 0x00ff;
|
||
|
}
|
||
|
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
static ssize_t codec_speedy_write(struct file *f,
|
||
|
const char __user *buf,
|
||
|
size_t len,
|
||
|
loff_t *off)
|
||
|
{
|
||
|
pr_info("sysfs driver: write()\n");
|
||
|
codec_write_reg(g_arg1, g_arg2, g_arg3);
|
||
|
return len;
|
||
|
}
|
||
|
|
||
|
static const struct file_operations pugs_fops = {
|
||
|
.owner = THIS_MODULE,
|
||
|
.open = codec_speedy_open,
|
||
|
.release = codec_speedy_close,
|
||
|
.read = codec_speedy_read,
|
||
|
.write = codec_speedy_write
|
||
|
};
|
||
|
|
||
|
static ssize_t codec_sysfs_show(struct class *cls,
|
||
|
struct class_attribute *attr,
|
||
|
char *buf)
|
||
|
{
|
||
|
codec_speedy_read(0, 0, 0, 0);
|
||
|
|
||
|
if (g_command == COMMAND_DUMP)
|
||
|
return sprintf(buf, "%s", reg_dump);
|
||
|
if (g_command == COMMAND_READ)
|
||
|
return sprintf(buf, "0x%x\n", g_arg3);
|
||
|
if (g_command == COMMAND_WRITE)
|
||
|
return sprintf(buf, "0x%x\n", g_arg3);
|
||
|
|
||
|
return sprintf(buf, "%s", reg_dump);
|
||
|
}
|
||
|
|
||
|
static ssize_t codec_sysfs_store(struct class *cls,
|
||
|
struct class_attribute *attr,
|
||
|
const char *buf,
|
||
|
size_t count)
|
||
|
{
|
||
|
int ret;
|
||
|
char input[MAX_STRING_LENGTH];
|
||
|
|
||
|
if (strlen(buf) > MAX_STRING_LENGTH) {
|
||
|
pr_info("%s() [ERROR] wrong input: %s\n", __func__, buf);
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
memcpy(input, buf, strlen(buf));
|
||
|
ret = command_parsing(input);
|
||
|
|
||
|
if (g_command == COMMAND_WRITE)
|
||
|
codec_speedy_write(0, 0, 0, 0);
|
||
|
|
||
|
return count;
|
||
|
}
|
||
|
|
||
|
static CLASS_ATTR_RW(codec_sysfs);
|
||
|
|
||
|
static int __init codec_speedy_init(void) /* Constructor */
|
||
|
{
|
||
|
int ret = 0;
|
||
|
|
||
|
pr_info("codec_speedy: registered");
|
||
|
if (alloc_chrdev_region(&codec_speedy_dev_t, 0, 1, "acpm") < 0)
|
||
|
return -1;
|
||
|
|
||
|
class_codec_speedy = class_create(THIS_MODULE, "codec_speedy");
|
||
|
if (class_codec_speedy == NULL) {
|
||
|
unregister_chrdev_region(codec_speedy_dev_t, 1);
|
||
|
return -1;
|
||
|
}
|
||
|
if (device_create(class_codec_speedy,
|
||
|
NULL,
|
||
|
codec_speedy_dev_t,
|
||
|
NULL, "acpm") == NULL) {
|
||
|
class_destroy(class_codec_speedy);
|
||
|
unregister_chrdev_region(codec_speedy_dev_t, 1);
|
||
|
return -1;
|
||
|
}
|
||
|
cdev_init(&c_dev, &pugs_fops);
|
||
|
if (cdev_add(&c_dev, codec_speedy_dev_t, 1) == FAIL) {
|
||
|
device_destroy(class_codec_speedy, codec_speedy_dev_t);
|
||
|
class_destroy(class_codec_speedy);
|
||
|
unregister_chrdev_region(codec_speedy_dev_t, 1);
|
||
|
return -1;
|
||
|
}
|
||
|
|
||
|
ret = class_create_file(class_codec_speedy,
|
||
|
&class_attr_codec_sysfs);
|
||
|
if (ret)
|
||
|
pr_info("%s() [Error] class_create_file\n", __func__);
|
||
|
|
||
|
g_command = COMMAND_DUMP;
|
||
|
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
static void __exit codec_speedy_exit(void) /* Destructor */
|
||
|
{
|
||
|
cdev_del(&c_dev);
|
||
|
device_destroy(class_codec_speedy, codec_speedy_dev_t);
|
||
|
class_destroy(class_codec_speedy);
|
||
|
unregister_chrdev_region(codec_speedy_dev_t, 1);
|
||
|
pr_info("codec_speedy: unregistered");
|
||
|
}
|
||
|
|
||
|
module_init(codec_speedy_init);
|
||
|
module_exit(codec_speedy_exit);
|
||
|
MODULE_LICENSE("GPL");
|
||
|
MODULE_AUTHOR("Jason Seong<jason.seong@samsung.com>");
|
||
|
MODULE_DESCRIPTION("Codec Speedy");
|