| /* |
| * Copyright (c) 2017 Maxim Integrated Products, Inc. |
| * |
| * 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. |
| */ |
| |
| #include <linux/module.h> |
| #include <linux/device.h> |
| #include <linux/miscdevice.h> |
| #include <linux/i2c.h> |
| #include <linux/stat.h> |
| #include <linux/slab.h> |
| #include <linux/fs.h> |
| #include <linux/delay.h> |
| #include <linux/uaccess.h> |
| |
| #ifdef CONFIG_COMPAT |
| #include <linux/compat.h> |
| #endif /* CONFIG_COMPAT */ |
| |
| #include <linux/ccic/max77705_debug.h> |
| |
| #define DEBUG_MXIM |
| #ifdef DEBUG_MXIM |
| #define msg_maxim(format, args...) \ |
| pr_info("[MXIM] %s: " format "\n", __func__, ## args) |
| #else |
| #define msg_maxim(format, args...) |
| #endif /* DEBUG_MXIM */ |
| |
| struct mxim_debug_registers mxim_debug_reg[] = { |
| { |
| .reg = MXIM_REG_UIC_HW_REV, |
| .val = 0, |
| .ignore = 0, |
| }, |
| { |
| .reg = MXIM_REG_UIC_FW_REV, |
| .val = 0, |
| .ignore = 0, |
| }, |
| { |
| .reg = MXIM_REG_USBC_IRQ, |
| .val = 0, |
| .ignore = 1, |
| }, |
| { |
| .reg = MXIM_REG_CC_IRQ, |
| .val = 0, |
| .ignore = 1, |
| }, |
| { |
| .reg = MXIM_REG_PD_IRQ, |
| .val = 0, |
| .ignore = 1, |
| }, |
| { |
| .reg = MXIM_REG_RSVD1, |
| .val = 0, |
| .ignore = 0, |
| }, |
| { |
| .reg = MXIM_REG_USBC_STATUS1, |
| .val = 0, |
| .ignore = 0, |
| }, |
| { |
| .reg = MXIM_REG_USBC_STATUS2, |
| .val = 0, |
| .ignore = 0, |
| }, |
| { |
| .reg = MXIM_REG_BC_STATUS, |
| .val = 0, |
| .ignore = 0, |
| }, |
| { |
| .reg = MXIM_REG_RSVD2, |
| .val = 0, |
| .ignore = 0, |
| }, |
| { |
| .reg = MXIM_REG_CC_STATUS1, |
| .val = 0, |
| .ignore = 0, |
| }, |
| { |
| .reg = MXIM_REG_CC_STATUS2, |
| .val = 0, |
| .ignore = 0, |
| }, |
| { |
| .reg = MXIM_REG_PD_STATUS1, |
| .val = 0, |
| .ignore = 0, |
| }, |
| { |
| .reg = MXIM_REG_PD_STATUS2, |
| .val = 0, |
| .ignore = 0, |
| }, |
| { |
| .reg = MXIM_REG_USBC_IRQM, |
| .val = 0, |
| .ignore = 0, |
| }, |
| { |
| .reg = MXIM_REG_CC_IRQM, |
| .val = 0, |
| .ignore = 0, |
| }, |
| { |
| .reg = MXIM_REG_PD_IRQM, |
| .val = 0, |
| .ignore = 0, |
| }, |
| }; |
| |
| struct mxim_debug_pdev *mxim_pdev; |
| |
| inline int mxim_debug_i2c_burst_write(unsigned char command, |
| unsigned char length, unsigned char *value) |
| { |
| int ret; |
| |
| /* Max length 32 bytes */ |
| ret = i2c_smbus_write_i2c_block_data(mxim_pdev->client, |
| command, length, value); |
| if (ret < 0) |
| msg_maxim("Failed to write as burst mode"); |
| |
| return ret; |
| } |
| |
| inline int mxim_debug_i2c_write(unsigned char command, |
| unsigned char value) |
| { |
| if (!mxim_pdev) |
| return -ENXIO; |
| |
| if (mxim_pdev->client == NULL) |
| return -ENXIO; |
| |
| return i2c_smbus_write_byte_data(mxim_pdev->client, |
| command, value); |
| } |
| |
| static int mxim_debug_i2c_burst_read(unsigned char command, |
| unsigned char length, unsigned char *value) |
| { |
| int ret; |
| |
| /* Max length 32 bytes */ |
| ret = i2c_smbus_read_i2c_block_data(mxim_pdev->client, |
| command, length, value); |
| if (ret < 0) |
| msg_maxim("Failed to read as burst mode"); |
| |
| return ret; |
| } |
| |
| static int mxim_debug_i2c_read(unsigned char reg, |
| unsigned char *value) |
| { |
| int ret = 0; |
| |
| if (!mxim_pdev) |
| return -ENXIO; |
| |
| if (mxim_pdev->client == NULL) |
| return -ENXIO; |
| |
| ret = i2c_smbus_read_byte_data(mxim_pdev->client, reg); |
| if (ret < 0) { |
| msg_maxim("Failed to read [reg:%02x]. ret=%d", |
| reg, ret); |
| } |
| |
| *value = ret; |
| |
| return ret; |
| } |
| |
| static ssize_t mxim_debug_reg_show(struct device *dev, |
| struct device_attribute *attr, char *buf) |
| { |
| unsigned char token[12] = { 0, }; |
| unsigned char dump[12 * (MXIM_REG_MAX + 1)] = { 0, }; |
| int index; |
| |
| sprintf(token, "reg val\n"); |
| strcat(dump, token); |
| for (index = 0; index < MXIM_REG_MAX; index++) { |
| if (mxim_debug_reg[index].ignore) |
| continue; |
| memset(token, 0x00, sizeof(token)); |
| mxim_debug_i2c_read(mxim_debug_reg[index].reg, |
| &mxim_debug_reg[index].val); |
| sprintf(token, "0x%02x 0x%02x\n", |
| mxim_debug_reg[index].reg, |
| mxim_debug_reg[index].val); |
| strcat(dump, token); |
| } |
| |
| return sprintf(buf, "%s", dump); |
| } |
| |
| static ssize_t mxim_debug_reg_store(struct device *dev, |
| struct device_attribute *attr, const char *buf, size_t size) |
| { |
| unsigned int reg = 0; |
| unsigned int value = 0; |
| unsigned char prev = 0; |
| unsigned char curr = 0; |
| int ret; |
| |
| ret = sscanf(buf, "%x%x", ®, &value); |
| if (!ret) { |
| msg_maxim("Failed to convert user data"); |
| goto error; |
| } |
| |
| mxim_debug_i2c_read((unsigned char)reg, &prev); |
| mxim_debug_i2c_write((unsigned char)reg, (unsigned char)value); |
| mxim_debug_i2c_read((unsigned char)reg, &curr); |
| |
| error: |
| return size; |
| } |
| |
| static DEVICE_ATTR(reg, 0664, |
| mxim_debug_reg_show, mxim_debug_reg_store); |
| |
| static ssize_t mxim_debug_opcode_show(struct device *dev, |
| struct device_attribute *attr, char *buf) |
| { |
| unsigned char data[OPCODE_READ_MAX_LENGTH] = { 0, }; |
| unsigned char dummy = 0x00; |
| char data_str[OPCODE_READ_MAX_LENGTH * 5 + 1] = { 0, }; |
| char dummy_str[5 + 1] = { 0, }; /* add null character */ |
| int index = 0; |
| |
| for (index = 0; index < OPCODE_READ_MAX_LENGTH; index++) { |
| data[index] = mxim_debug_i2c_read( |
| OPCODE_READ_START_ADDR + index, |
| &dummy); |
| sprintf(dummy_str, "0x%02x ", data[index]); |
| strcat(data_str, dummy_str); |
| } |
| |
| return sprintf(buf, "%s\n", data_str); |
| } |
| |
| static ssize_t mxim_debug_opcode_store(struct device *dev, |
| struct device_attribute *attr, const char *buf, size_t size) |
| { |
| unsigned int data[OPCODE_WRITE_MAX_LENGTH] = { 0, }; |
| int index; |
| int ret; |
| |
| ret = sscanf(buf, |
| "%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x", |
| &data[0], /* AP_DATAOUT0 */ |
| &data[1], &data[2], &data[3], &data[4], |
| &data[5], &data[6], &data[7], &data[8], |
| &data[9], &data[10], &data[11], &data[12], |
| &data[13], &data[14], &data[15], &data[16], |
| &data[17], &data[18], &data[19], &data[20], |
| &data[21], &data[22], &data[23], &data[24], |
| &data[25], &data[26], &data[27], &data[28], |
| &data[29], &data[30], &data[31], &data[32]); |
| if (!ret) { |
| msg_maxim("Failed to convert user data"); |
| goto error; |
| } |
| |
| for (index = 0; index < OPCODE_WRITE_MAX_LENGTH; index++) |
| mxim_debug_i2c_write(OPCODE_WRITE_START_ADDR + index, |
| (unsigned char)data[index]); |
| |
| error: |
| return size; |
| } |
| |
| static DEVICE_ATTR(opcode, 0664, |
| mxim_debug_opcode_show, mxim_debug_opcode_store); |
| |
| static struct attribute *mxim_debug_attr[] = { |
| &dev_attr_opcode.attr, |
| &dev_attr_reg.attr, |
| NULL, |
| }; |
| |
| static struct attribute_group mxim_debug_attr_grp = { |
| .attrs = mxim_debug_attr, |
| }; |
| |
| static int mxim_debug_open(struct inode *inode, struct file *filep) |
| { |
| return 0; |
| } |
| |
| static long mxim_debug_ioctl_handler(struct file *file, |
| unsigned int cmd, unsigned int arg, |
| void __user *argp) |
| { |
| unsigned char reg = 0; |
| unsigned char val = 0; |
| int ret = -EINVAL; |
| |
| mutex_lock(&mxim_pdev->lock); |
| |
| switch (cmd) { |
| case MXIM_DEBUG_OPCODE_WRITE: |
| /* copy wdata from user */ |
| if (copy_from_user(mxim_pdev->opcode_wdata, |
| argp, OPCODE_WRITE_MAX_LENGTH)) { |
| msg_maxim("Failed to copy data buffer from user"); |
| ret = -EFAULT; |
| goto error; |
| } |
| |
| /* OPCode */ |
| mxim_debug_i2c_write(OPCODE_WRITE_START_ADDR, |
| mxim_pdev->opcode_wdata[0]); |
| |
| /* AP OUT Data */ |
| ret = mxim_debug_i2c_burst_write(OPCODE_WRITE_START_ADDR + 1, |
| (OPCODE_WRITE_MAX_LENGTH - 1), |
| &mxim_pdev->opcode_wdata[1]); |
| break; |
| case MXIM_DEBUG_OPCODE_READ: |
| /* OPCode */ |
| mxim_debug_i2c_read(OPCODE_READ_START_ADDR, |
| &mxim_pdev->opcode_rdata[0]); |
| |
| /* AP IN Data */ |
| ret = mxim_debug_i2c_burst_read(OPCODE_READ_START_ADDR + 1, |
| (OPCODE_READ_MAX_LENGTH - 1), |
| &mxim_pdev->opcode_rdata[1]); |
| |
| /* copy rdata to user */ |
| if (copy_to_user(argp, mxim_pdev->opcode_rdata, |
| OPCODE_READ_MAX_LENGTH)) { |
| msg_maxim("Failed to copy data buffer to user"); |
| ret = -EFAULT; |
| goto error; |
| } |
| break; |
| case MXIM_DEBUG_REG_WRITE: |
| reg = (unsigned char)(arg & 0x000000FF); |
| val = (unsigned char)((arg & 0x0000FF00) >> 8); |
| ret = mxim_debug_i2c_write(reg, val); |
| break; |
| case MXIM_DEBUG_REG_READ: |
| reg = (unsigned int)(arg & 0x000000FF); |
| mxim_debug_i2c_read(reg, &val); |
| ret = val; |
| if (copy_to_user(argp, &val, sizeof(val))) |
| goto error; |
| break; |
| case MXIM_DEBUG_REG_DUMP: |
| break; |
| default: |
| break; |
| } |
| error: |
| mutex_unlock(&mxim_pdev->lock); |
| return ret; |
| } |
| |
| static long mxim_debug_ioctl(struct file *file, |
| unsigned int cmd, unsigned long arg) |
| { |
| return mxim_debug_ioctl_handler(file, cmd, arg, |
| (void __user *)arg); |
| } |
| |
| #ifdef CONFIG_COMPAT |
| static long mxim_debug_compat_ioctl(struct file *file, |
| unsigned int cmd, unsigned long arg) |
| { |
| return mxim_debug_ioctl_handler(file, cmd, arg, |
| (void __user *)(unsigned long)arg); |
| } |
| #endif |
| |
| static ssize_t mxim_debug_read(struct file *filep, char __user *buf, |
| size_t count, loff_t *ppos) |
| { |
| int ret = 0; |
| |
| if (!mxim_pdev) |
| goto error; |
| |
| ret = copy_to_user(buf, mxim_pdev->opcode_rdata, |
| count > OPCODE_READ_MAX_LENGTH ? |
| OPCODE_READ_MAX_LENGTH : count); |
| if (ret) |
| msg_maxim("Failed to copy user buffer"); |
| |
| error: |
| return ret; |
| } |
| |
| static ssize_t mxim_debug_write(struct file *filep, const char __user *buf, |
| size_t count, loff_t *ppos) |
| { |
| int ret = 0; |
| |
| if (!mxim_pdev) |
| goto error; |
| |
| if (copy_from_user(mxim_pdev->opcode_wdata, buf, |
| OPCODE_READ_MAX_LENGTH)) { |
| msg_maxim("Failed to copy user buffer"); |
| goto error; |
| } |
| |
| error: |
| return ret; |
| } |
| |
| void mxim_debug_set_i2c_client(struct i2c_client *client) |
| { |
| if (mxim_pdev) { |
| mxim_pdev->client = client; |
| msg_maxim("Set i2c_client. %pK", mxim_pdev->client); |
| } else |
| msg_maxim("Failed to set i2c_client."); |
| } |
| EXPORT_SYMBOL_GPL(mxim_debug_set_i2c_client); |
| |
| static const struct file_operations mxim_debug_fops = { |
| .owner = THIS_MODULE, |
| .open = mxim_debug_open, |
| .release = NULL, |
| .unlocked_ioctl = mxim_debug_ioctl, |
| #ifdef CONFIG_COMPAT |
| .compat_ioctl = mxim_debug_compat_ioctl, |
| #endif /* CONFIG_COMPAT */ |
| .read = mxim_debug_read, |
| .write = mxim_debug_write, |
| .mmap = NULL, |
| .poll = NULL, |
| .fasync = NULL, |
| .llseek = NULL, |
| }; |
| static struct miscdevice mxim_debug_miscdev = { |
| .minor = MISC_DYNAMIC_MINOR, |
| .name = "mxim_dev", |
| .fops = &mxim_debug_fops, |
| }; |
| |
| void mxim_debug_exit(void) |
| { |
| if (mxim_pdev) { |
| mutex_destroy(&mxim_pdev->lock); |
| sysfs_remove_group(&mxim_pdev->dev->kobj, &mxim_debug_attr_grp); |
| device_destroy(mxim_pdev->class, 1); |
| class_destroy(mxim_pdev->class); |
| kfree(mxim_pdev); |
| mxim_pdev = NULL; |
| } |
| |
| misc_deregister(&mxim_debug_miscdev); |
| } |
| EXPORT_SYMBOL_GPL(mxim_debug_exit); |
| |
| int mxim_debug_init(void) |
| { |
| struct mxim_debug_pdev *pdev; |
| int ret; |
| |
| ret = misc_register(&mxim_debug_miscdev); |
| if (ret) { |
| msg_maxim("Failed to register miscdevice"); |
| goto error; |
| } |
| |
| pdev = kzalloc(sizeof(struct mxim_debug_pdev), GFP_KERNEL); |
| if (!pdev) { |
| msg_maxim("Failed to allocate memory"); |
| ret = -ENOMEM; |
| goto error; |
| } |
| |
| mxim_pdev = pdev; |
| |
| mutex_init(&pdev->lock); |
| |
| pdev->class = class_create(THIS_MODULE, "mxim"); |
| if (pdev->class) { |
| pdev->dev = device_create(pdev->class, NULL, 1, NULL, "debug0"); |
| if (!IS_ERR(pdev->dev)) { |
| if (sysfs_create_group(&pdev->dev->kobj, |
| &mxim_debug_attr_grp)) |
| msg_maxim("Failed to create sysfs group. %d", |
| ret); |
| } |
| } |
| |
| error: |
| return ret; |
| } |
| EXPORT_SYMBOL_GPL(mxim_debug_init); |
| |
| MODULE_DESCRIPTION("MAXIM Debug Module"); |
| MODULE_LICENSE("GPL"); |