1
0
mirror of https://github.com/physwizz/a155-U-u1.git synced 2024-11-19 13:27:49 +00:00
a155-U-u1/kernel-5.10/drivers/misc/mediatek/subpmic/extdev_io_class.c
2024-03-11 06:53:12 +11:00

337 lines
8.0 KiB
C

// SPDX-License-Identifier: GPL-2.0
/*
* Copyright (c) 2021 MediaTek Inc.
*/
#include <linux/device.h>
#include <linux/init.h>
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/slab.h>
#include <linux/extdev_io_class.h>
#define PREALLOC_RBUFFER_SIZE (32)
#define PREALLOC_WBUFFER_SIZE (1000)
static struct class *extdev_io_class;
static ssize_t extdev_io_show(struct device *dev,
struct device_attribute *attr,
char *buf);
static ssize_t extdev_io_store(struct device *dev,
struct device_attribute *attr,
const char *buf, size_t count);
#define EXTDEV_IO_DEVICE_ATTR(_name, _mode) \
{ \
.attr = { .name = #_name, .mode = _mode }, \
.show = extdev_io_show, \
.store = extdev_io_store, \
}
static struct device_attribute extdev_io_device_attributes[] = {
EXTDEV_IO_DEVICE_ATTR(reg, 0644),
EXTDEV_IO_DEVICE_ATTR(size, 0644),
EXTDEV_IO_DEVICE_ATTR(data, 0644),
EXTDEV_IO_DEVICE_ATTR(type, 0444),
EXTDEV_IO_DEVICE_ATTR(lock, 0644),
};
enum {
EXTDEV_IO_DESC_REG,
EXTDEV_IO_DESC_SIZE,
EXTDEV_IO_DESC_DATA,
EXTDEV_IO_DESC_TYPE,
EXTDEV_IO_DESC_LOCK,
};
static int extdev_io_read(struct extdev_io_device *extdev, char *buf)
{
int cnt = 0, i, ret;
void *buffer;
u8 *data;
struct extdev_desc *desc = extdev->desc;
if (extdev->data_buffer_size < extdev->size) {
buffer = kzalloc(extdev->size, GFP_KERNEL);
if (!buffer)
return -ENOMEM;
kfree(extdev->data_buffer);
extdev->data_buffer = buffer;
extdev->data_buffer_size = extdev->size;
}
/* read transfer */
if (!desc->io_read)
return -EPERM;
ret = desc->io_read(desc->rmap, extdev->reg, extdev->data_buffer,
extdev->size);
if (ret < 0)
return ret;
data = extdev->data_buffer;
cnt = snprintf(buf + cnt, 256, "0x");
for (i = 0; i < extdev->size; i++)
cnt += snprintf(buf + cnt, 256, "%02x,", *(data + i));
cnt = snprintf(buf + cnt, 256, "\n");
return ret;
}
static int extdev_io_write(struct extdev_io_device *extdev, const char *buf_internal, ssize_t cnt)
{
void *buffer;
u8 *pdata;
char buf[PREALLOC_WBUFFER_SIZE + 1], *token, *cur;
int val_cnt = 0, ret;
struct extdev_desc *desc = extdev->desc;
if (cnt > PREALLOC_WBUFFER_SIZE)
return -ENOMEM;
memcpy(buf, buf_internal, cnt);
buf[cnt] = 0;
/* buffer size check */
if (extdev->data_buffer_size < extdev->size) {
buffer = kzalloc(extdev->size, GFP_KERNEL);
if (!buffer)
return -ENOMEM;
kfree(extdev->data_buffer);
extdev->data_buffer = buffer;
extdev->data_buffer_size = extdev->size;
}
/* data parsing */
cur = buf;
pdata = extdev->data_buffer;
while ((token = strsep(&cur, ",\n")) != NULL) {
if (!*token)
break;
if (val_cnt++ >= extdev->size)
break;
if (kstrtou8(token, 16, pdata++))
return -EINVAL;
}
if (val_cnt != extdev->size)
return -EINVAL;
/* write transfer */
if (!desc->io_write)
return -EPERM;
ret = desc->io_write(desc->rmap, extdev->reg, extdev->data_buffer, extdev->size);
return (ret < 0) ? ret : cnt;
}
static ssize_t extdev_io_show(struct device *dev,
struct device_attribute *attr,
char *buf)
{
struct extdev_io_device *extdev = dev_get_drvdata(dev);
const ptrdiff_t offset = attr - extdev_io_device_attributes;
int ret = 0;
mutex_lock(&extdev->io_lock);
switch (offset) {
case EXTDEV_IO_DESC_REG:
snprintf(buf, 256, "0x%04x\n", extdev->reg);
break;
case EXTDEV_IO_DESC_SIZE:
snprintf(buf, 256, "%d\n", extdev->size);
break;
case EXTDEV_IO_DESC_DATA:
ret = extdev_io_read(extdev, buf);
break;
case EXTDEV_IO_DESC_TYPE:
snprintf(buf, 256, "%s\n", extdev->desc->typestr);
break;
case EXTDEV_IO_DESC_LOCK:
snprintf(buf, 256, "%d\n", extdev->access_lock);
break;
default:
ret = -EINVAL;
break;
}
mutex_unlock(&extdev->io_lock);
return ret < 0 ? ret : strlen(buf);
}
static int get_parameters(char *buf, long int *param1, int num_of_par)
{
char *token;
int base, cnt;
token = strsep(&buf, " ");
for (cnt = 0; cnt < num_of_par; cnt++) {
if (token != NULL) {
if ((token[1] == 'x') || (token[1] == 'X'))
base = 16;
else
base = 10;
if (kstrtoul(token, base, &param1[cnt]) != 0)
return -EINVAL;
token = strsep(&buf, " ");
}
else
return -EINVAL;
}
return 0;
}
static ssize_t extdev_io_store(struct device *dev,
struct device_attribute *attr,
const char *buf, size_t count)
{
struct extdev_io_device *extdev = dev_get_drvdata(dev);
const ptrdiff_t offset = attr - extdev_io_device_attributes;
long int val;
int ret = 0;
mutex_lock(&extdev->io_lock);
switch (offset) {
case EXTDEV_IO_DESC_REG:
get_parameters((char *)buf, &val, 1);
extdev->reg = val;
break;
case EXTDEV_IO_DESC_SIZE:
get_parameters((char *)buf, &val, 1);
extdev->size = val;
break;
case EXTDEV_IO_DESC_DATA:
ret = extdev_io_write(extdev, buf, count);
break;
case EXTDEV_IO_DESC_LOCK:
get_parameters((char *)buf, &val, 1);
if (!!val == extdev->access_lock)
ret = -EFAULT;
else
extdev->access_lock = !!val;
break;
default:
ret = -EINVAL;
}
mutex_unlock(&extdev->io_lock);
return ret < 0 ? ret : count;
}
struct extdev_io_device * extdev_io_device_register(struct device *parent,
struct extdev_desc *desc)
{
struct extdev_io_device *extdev;
if (!parent) {
pr_err("%s: Expected proper parent device\n", __func__);
return ERR_PTR(-EINVAL);
}
if (!desc || !desc->devname || !desc->typestr || !desc->rmap)
return ERR_PTR(-EINVAL);
extdev = devm_kzalloc(parent, sizeof(*extdev), GFP_KERNEL);
if (!extdev) {
pr_err("%s: failed to alloc extdev\n", __func__);
return ERR_PTR(-ENOMEM);
}
extdev->desc = desc;
mutex_init(&extdev->io_lock);
/* for MTK engineer setting */
extdev->size = 1;
extdev->data_buffer_size = PREALLOC_RBUFFER_SIZE;
extdev->data_buffer = kzalloc(PREALLOC_RBUFFER_SIZE, GFP_KERNEL);
if (!extdev->data_buffer) {
kfree(extdev);
return ERR_PTR(-ENOMEM);
}
extdev->dev = device_create_with_groups(extdev_io_class, parent, 0, extdev, NULL, "%s",
desc->dirname);
if (IS_ERR(extdev->dev)) {
kfree(extdev->data_buffer);
return ERR_CAST(extdev->dev);
}
return extdev;
}
EXPORT_SYMBOL_GPL(extdev_io_device_register);
void extdev_io_device_unregister(struct extdev_io_device *extdev)
{
device_unregister(extdev->dev);
kfree(extdev->data_buffer);
mutex_destroy(&extdev->io_lock);
}
EXPORT_SYMBOL_GPL(extdev_io_device_unregister);
static void devm_extdev_io_device_release(struct device *dev, void *res)
{
struct extdev_io_device **extdev = res;
extdev_io_device_unregister(*extdev);
}
struct extdev_io_device * devm_extdev_io_device_register(struct device *parent,
struct extdev_desc *desc)
{
struct extdev_io_device **ptr, *extdev;
ptr = devres_alloc(devm_extdev_io_device_release, sizeof(*ptr), GFP_KERNEL);
if (!ptr)
return ERR_PTR(-ENOMEM);
extdev = extdev_io_device_register(parent, desc);
if (IS_ERR(extdev)) {
devres_free(ptr);
} else {
*ptr = extdev;
devres_add(parent, ptr);
}
return extdev;
}
EXPORT_SYMBOL_GPL(devm_extdev_io_device_register);
static struct attribute *extdev_io_class_attrs[] = {
&extdev_io_device_attributes[0].attr,
&extdev_io_device_attributes[1].attr,
&extdev_io_device_attributes[2].attr,
&extdev_io_device_attributes[3].attr,
&extdev_io_device_attributes[4].attr,
NULL,
};
static const struct attribute_group extdev_io_attr_group = {
.attrs = extdev_io_class_attrs,
};
static const struct attribute_group *extdev_io_attr_groups[] = {
&extdev_io_attr_group,
NULL
};
static int __init extdev_io_class_init(void)
{
pr_info("%s\n", __func__);
extdev_io_class = class_create(THIS_MODULE, "extdev_io");
if (IS_ERR(extdev_io_class)) {
pr_err("Unable to create extdev_io class(%d)\n", PTR_ERR(extdev_io_class));
return PTR_ERR(extdev_io_class);
}
extdev_io_class->dev_groups = extdev_io_attr_groups;
pr_info("extdev_io class init OK\n");
return 0;
}
static void __exit extdev_io_class_exit(void)
{
class_destroy(extdev_io_class);
pr_info("extdev_io class deinit OK\n");
}
subsys_initcall(extdev_io_class_init);
module_exit(extdev_io_class_exit);
MODULE_DESCRIPTION("Extdev io class");
MODULE_AUTHOR("Gene Chen <gene_chen@richtek.com>");
MODULE_LICENSE("GPL");