6db4831e98
Android 14
349 lines
8.3 KiB
C
349 lines
8.3 KiB
C
// SPDX-License-Identifier: GPL-2.0
|
|
/*
|
|
* Copyright (c) 2019 MediaTek Inc.
|
|
*/
|
|
|
|
#include <linux/init.h>
|
|
#include <linux/module.h>
|
|
#include <linux/kernel.h>
|
|
#include <linux/of.h>
|
|
#include <linux/platform_device.h>
|
|
#include <linux/mutex.h>
|
|
#include <linux/slab.h>
|
|
#include <linux/uaccess.h>
|
|
#include <linux/seq_file.h>
|
|
#include <linux/debugfs.h>
|
|
#include <linux/regmap.h>
|
|
|
|
struct dbg_internal {
|
|
struct dentry *rt_root;
|
|
struct dentry *ic_root;
|
|
bool rt_dir_create;
|
|
struct mutex io_lock;
|
|
u16 reg;
|
|
u16 size;
|
|
u16 data_buffer_size;
|
|
void *data_buffer;
|
|
bool access_lock;
|
|
};
|
|
|
|
struct dbg_info {
|
|
const char *dirname;
|
|
const char *devname;
|
|
const char *typestr;
|
|
void *io_drvdata;
|
|
int (*io_read)(void *drvdata, u16 reg, void *val, u16 size);
|
|
int (*io_write)(void *drvdata, u16 reg, const void *val, u16 size);
|
|
struct dbg_internal internal;
|
|
};
|
|
|
|
struct mt6362_dbg_info {
|
|
struct device *dev;
|
|
struct dbg_info dbg_info;
|
|
};
|
|
|
|
#ifdef CONFIG_DEBUG_FS
|
|
/* reg/size/data/bustype */
|
|
#define PREALLOC_RBUFFER_SIZE (32)
|
|
#define PREALLOC_WBUFFER_SIZE (1000)
|
|
|
|
static int data_debug_show(struct seq_file *s, void *data)
|
|
{
|
|
struct dbg_info *di = s->private;
|
|
struct dbg_internal *d = &di->internal;
|
|
void *buffer;
|
|
u8 *pdata;
|
|
int i, ret;
|
|
|
|
if (d->data_buffer_size < d->size) {
|
|
buffer = kzalloc(d->size, GFP_KERNEL);
|
|
if (!buffer)
|
|
return -ENOMEM;
|
|
kfree(d->data_buffer);
|
|
d->data_buffer = buffer;
|
|
d->data_buffer_size = d->size;
|
|
}
|
|
/* read transfer */
|
|
if (!di->io_read)
|
|
return -EPERM;
|
|
ret = di->io_read(di->io_drvdata, d->reg, d->data_buffer, d->size);
|
|
if (ret < 0)
|
|
return ret;
|
|
pdata = d->data_buffer;
|
|
seq_puts(s, "0x");
|
|
for (i = 0; i < d->size; i++)
|
|
seq_printf(s, "%02x,", *(pdata + i));
|
|
seq_puts(s, "\n");
|
|
return 0;
|
|
}
|
|
|
|
static int data_debug_open(struct inode *inode, struct file *file)
|
|
{
|
|
return single_open(file, data_debug_show, inode->i_private);
|
|
}
|
|
|
|
static ssize_t data_debug_write(struct file *file,
|
|
const char __user *user_buf,
|
|
size_t cnt, loff_t *loff)
|
|
{
|
|
struct seq_file *seq = file->private_data;
|
|
struct dbg_info *di = seq->private;
|
|
struct dbg_internal *d = &di->internal;
|
|
void *buffer;
|
|
u8 *pdata;
|
|
char buf[PREALLOC_WBUFFER_SIZE + 1], *token, *cur;
|
|
int val_cnt = 0, ret;
|
|
|
|
if (cnt > PREALLOC_WBUFFER_SIZE)
|
|
return -ENOMEM;
|
|
if (copy_from_user(buf, user_buf, cnt))
|
|
return -EFAULT;
|
|
buf[cnt] = 0;
|
|
/* buffer size check */
|
|
if (d->data_buffer_size < d->size) {
|
|
buffer = kzalloc(d->size, GFP_KERNEL);
|
|
if (!buffer)
|
|
return -ENOMEM;
|
|
kfree(d->data_buffer);
|
|
d->data_buffer = buffer;
|
|
d->data_buffer_size = d->size;
|
|
}
|
|
/* data parsing */
|
|
cur = buf;
|
|
pdata = d->data_buffer;
|
|
while ((token = strsep(&cur, ",\n")) != NULL) {
|
|
if (!*token)
|
|
break;
|
|
if (val_cnt++ >= d->size)
|
|
break;
|
|
if (kstrtou8(token, 16, pdata++))
|
|
return -EINVAL;
|
|
}
|
|
if (val_cnt != d->size)
|
|
return -EINVAL;
|
|
/* write transfer */
|
|
if (!di->io_write)
|
|
return -EPERM;
|
|
ret = di->io_write(di->io_drvdata, d->reg, d->data_buffer, d->size);
|
|
return (ret < 0) ? ret : cnt;
|
|
}
|
|
|
|
static const struct file_operations data_debug_fops = {
|
|
.open = data_debug_open,
|
|
.read = seq_read,
|
|
.write = data_debug_write,
|
|
.llseek = seq_lseek,
|
|
.release = single_release,
|
|
};
|
|
|
|
static int type_debug_show(struct seq_file *s, void *data)
|
|
{
|
|
struct dbg_info *di = s->private;
|
|
|
|
seq_printf(s, "%s,%s\n", di->typestr, di->devname);
|
|
return 0;
|
|
}
|
|
|
|
static int type_debug_open(struct inode *inode, struct file *file)
|
|
{
|
|
return single_open(file, type_debug_show, inode->i_private);
|
|
}
|
|
|
|
static const struct file_operations type_debug_fops = {
|
|
.open = type_debug_open,
|
|
.read = seq_read,
|
|
.llseek = seq_lseek,
|
|
.release = single_release,
|
|
};
|
|
|
|
static ssize_t lock_debug_read(struct file *file,
|
|
char __user *user_buf, size_t cnt, loff_t *loff)
|
|
{
|
|
struct dbg_info *di = file->private_data;
|
|
struct dbg_internal *d = &di->internal;
|
|
char buf[10];
|
|
bool lock;
|
|
|
|
mutex_lock(&d->io_lock);
|
|
lock = d->access_lock;
|
|
mutex_unlock(&d->io_lock);
|
|
|
|
snprintf(buf, sizeof(buf), "%d\n", lock);
|
|
return simple_read_from_buffer(user_buf, cnt, loff, buf, strlen(buf));
|
|
}
|
|
|
|
static ssize_t lock_debug_write(struct file *file,
|
|
const char __user *user_buf,
|
|
size_t cnt, loff_t *loff)
|
|
{
|
|
struct dbg_info *di = file->private_data;
|
|
struct dbg_internal *d = &di->internal;
|
|
u32 lock;
|
|
int ret;
|
|
|
|
ret = kstrtou32_from_user(user_buf, cnt, 0, &lock);
|
|
if (ret < 0)
|
|
return ret;
|
|
mutex_lock(&d->io_lock);
|
|
if (!!lock == d->access_lock)
|
|
ret = -EFAULT;
|
|
d->access_lock = !!lock;
|
|
mutex_unlock(&d->io_lock);
|
|
return (ret < 0) ? ret : cnt;
|
|
}
|
|
|
|
static const struct file_operations lock_debug_fops = {
|
|
.open = simple_open,
|
|
.read = lock_debug_read,
|
|
.write = lock_debug_write,
|
|
};
|
|
|
|
static int generic_debugfs_init(struct dbg_info *di)
|
|
{
|
|
struct dbg_internal *d = &di->internal;
|
|
|
|
/* valid check */
|
|
if (!di->dirname || !di->devname || !di->typestr)
|
|
return -EINVAL;
|
|
d->data_buffer_size = PREALLOC_RBUFFER_SIZE;
|
|
/* for MTK engineer setting */
|
|
d->size = 1;
|
|
d->data_buffer = kzalloc(PREALLOC_RBUFFER_SIZE, GFP_KERNEL);
|
|
if (!d->data_buffer)
|
|
return -ENOMEM;
|
|
/* create debugfs */
|
|
d->rt_root = debugfs_lookup("ext_dev_io", NULL);
|
|
if (!d->rt_root) {
|
|
d->rt_root = debugfs_create_dir("ext_dev_io", NULL);
|
|
if (!d->rt_root)
|
|
return -ENODEV;
|
|
d->rt_dir_create = true;
|
|
}
|
|
mutex_init(&d->io_lock);
|
|
d->ic_root = debugfs_create_dir(di->dirname, d->rt_root);
|
|
if (!d->ic_root)
|
|
goto err_cleanup_rt;
|
|
if (!debugfs_create_u16("reg", 0644, d->ic_root, &d->reg))
|
|
goto err_cleanup_ic;
|
|
if (!debugfs_create_u16("size", 0644, d->ic_root, &d->size))
|
|
goto err_cleanup_ic;
|
|
if (!debugfs_create_file("data", 0644,
|
|
d->ic_root, di, &data_debug_fops))
|
|
goto err_cleanup_ic;
|
|
if (!debugfs_create_file("type", 0444,
|
|
d->ic_root, di, &type_debug_fops))
|
|
goto err_cleanup_ic;
|
|
if (!debugfs_create_file("lock", 0644,
|
|
d->ic_root, di, &lock_debug_fops))
|
|
goto err_cleanup_ic;
|
|
return 0;
|
|
err_cleanup_ic:
|
|
debugfs_remove_recursive(d->ic_root);
|
|
err_cleanup_rt:
|
|
mutex_destroy(&d->io_lock);
|
|
if (d->rt_dir_create)
|
|
debugfs_remove_recursive(d->rt_root);
|
|
kfree(d->data_buffer);
|
|
return -ENODEV;
|
|
}
|
|
|
|
static void generic_debugfs_exit(struct dbg_info *di)
|
|
{
|
|
struct dbg_internal *d = &di->internal;
|
|
|
|
debugfs_remove_recursive(d->ic_root);
|
|
mutex_destroy(&d->io_lock);
|
|
if (d->rt_dir_create)
|
|
debugfs_remove_recursive(d->rt_root);
|
|
kfree(d->data_buffer);
|
|
}
|
|
#else
|
|
static inline int generic_debugfs_init(struct dbg_info *di)
|
|
{
|
|
return 0;
|
|
}
|
|
|
|
static inline void generic_debugfs_exit(struct dbg_info *di) {}
|
|
#endif
|
|
|
|
static int mt6362_dbg_io_read(void *drvdata, u16 reg, void *val, u16 size)
|
|
{
|
|
return regmap_bulk_read((struct regmap *)drvdata, reg, val, size);
|
|
}
|
|
|
|
static int mt6362_dbg_io_write(void *drvdata,
|
|
u16 reg, const void *val, u16 size)
|
|
{
|
|
return regmap_bulk_write((struct regmap *)drvdata, reg, val, size);
|
|
}
|
|
|
|
static int mt6362_dbg_probe(struct platform_device *pdev)
|
|
{
|
|
struct mt6362_dbg_info *mdi;
|
|
struct regmap *regmap;
|
|
int ret;
|
|
|
|
mdi = devm_kzalloc(&pdev->dev, sizeof(*mdi), GFP_KERNEL);
|
|
if (!mdi)
|
|
return -ENOMEM;
|
|
|
|
mdi->dev = &pdev->dev;
|
|
platform_set_drvdata(pdev, mdi);
|
|
|
|
regmap = dev_get_regmap(pdev->dev.parent, NULL);
|
|
if (!regmap) {
|
|
dev_err(&pdev->dev, "failed to allocate regmap\n");
|
|
return -ENODEV;
|
|
}
|
|
|
|
/* debugfs interface */
|
|
mdi->dbg_info.dirname = devm_kasprintf(&pdev->dev,
|
|
GFP_KERNEL, "MT6362.%s",
|
|
dev_name(pdev->dev.parent));
|
|
mdi->dbg_info.devname = dev_name(pdev->dev.parent);
|
|
mdi->dbg_info.typestr = devm_kasprintf(&pdev->dev,
|
|
GFP_KERNEL, "SPMI,MT6362");
|
|
mdi->dbg_info.io_drvdata = regmap;
|
|
mdi->dbg_info.io_read = mt6362_dbg_io_read;
|
|
mdi->dbg_info.io_write = mt6362_dbg_io_write;
|
|
ret = generic_debugfs_init(&mdi->dbg_info);
|
|
if (ret < 0)
|
|
return ret;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int mt6362_dbg_remove(struct platform_device *pdev)
|
|
{
|
|
struct mt6362_dbg_info *mdi = platform_get_drvdata(pdev);
|
|
|
|
generic_debugfs_exit(&mdi->dbg_info);
|
|
return 0;
|
|
}
|
|
|
|
static const struct platform_device_id mt6362_dbg_id[] = {
|
|
{ "mt6362-dbg", 0 },
|
|
{ },
|
|
};
|
|
MODULE_DEVICE_TABLE(platform, mt6362_dbg_id);
|
|
|
|
static const struct of_device_id mt6362_dbg_of_id[] = {
|
|
{ .compatible = "mediatek,mt6362-dbg", },
|
|
{ },
|
|
};
|
|
MODULE_DEVICE_TABLE(of, mt6362_dbg_of_id);
|
|
|
|
static struct platform_driver mt6362_dbg_driver = {
|
|
.driver = {
|
|
.name = "mt6362_dbg",
|
|
.of_match_table = of_match_ptr(mt6362_dbg_of_id),
|
|
},
|
|
.probe = mt6362_dbg_probe,
|
|
.remove = mt6362_dbg_remove,
|
|
.id_table = mt6362_dbg_id,
|
|
};
|
|
module_platform_driver(mt6362_dbg_driver);
|
|
|
|
MODULE_LICENSE("GPL");
|
|
MODULE_AUTHOR("cy_huang <cy_huang@richtek.com>");
|