blob: 6eb5de8a6f38352eb124384f837698291f52478a [file] [log] [blame]
/*
* 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", &reg, &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");