/* * 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 #include #include #include #include #include #include #include #include #include #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"); MODULE_DESCRIPTION("Codec Speedy");