// SPDX-License-Identifier: GPL-2.0 /* * Copyright (c) 2019 MediaTek Inc. */ #include #include #include #include #include #include #include #include #include #include #include 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 ");