957 lines
25 KiB
C
957 lines
25 KiB
C
|
/*
|
||
|
* Rcihtek SPM Class Driver
|
||
|
*
|
||
|
* Copyright (C) 2019, Richtek Technology Corp.
|
||
|
* Author: CY Hunag <cy_huang@richtek.com>
|
||
|
*
|
||
|
* 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/init.h>
|
||
|
#include <linux/device.h>
|
||
|
#include <linux/module.h>
|
||
|
#include <linux/kernel.h>
|
||
|
#include <linux/slab.h>
|
||
|
#include <linux/uaccess.h>
|
||
|
#include <linux/kthread.h>
|
||
|
#include <linux/delay.h>
|
||
|
|
||
|
#include <sound/sec_synchronized_ipc_richtek.h>
|
||
|
#include "richtek_spm_cls.h"
|
||
|
|
||
|
static struct class *richtek_spm_class;
|
||
|
static int trig_status;
|
||
|
static int cali_status;
|
||
|
|
||
|
enum {
|
||
|
RICHTEK_SPM_DEV_RSPK = 0,
|
||
|
RICHTEK_SPM_DEV_INDEX,
|
||
|
RICHTEK_SPM_DEV_SPKIDX,
|
||
|
RICHTEK_SPM_DEV_PARAMS,
|
||
|
RICHTEK_SPM_DEV_PARAM_ITEMS,
|
||
|
RICHTEK_SPM_DEV_IPC_GO,
|
||
|
RICHTEK_SPM_DEV_TMAX,
|
||
|
RICHTEK_SPM_DEV_TMAXCNT,
|
||
|
RICHTEK_SPM_DEV_XMAX,
|
||
|
RICHTEK_SPM_DEV_XMAXCNT,
|
||
|
RICHTEK_SPM_DEV_TMAXKEEP,
|
||
|
RICHTEK_SPM_DEV_XMAXKEEP,
|
||
|
RICHTEK_SPM_DEV_VVALIDATION_REAL_POWER,
|
||
|
RICHTEK_SPM_DEV_MAX,
|
||
|
};
|
||
|
|
||
|
enum {
|
||
|
CMD_READ = 0,
|
||
|
CMD_WRITE,
|
||
|
CMD_MAX,
|
||
|
};
|
||
|
|
||
|
struct spm_param_item {
|
||
|
u32 index;
|
||
|
s32 param_items;
|
||
|
s32 offset;
|
||
|
};
|
||
|
|
||
|
static const struct spm_param_item ampon_wparam_items[] = {
|
||
|
{ 56, 1, offsetof(struct richtek_spm_classdev, rspk)},
|
||
|
{ 61, 1, offsetof(struct richtek_spm_classdev, t0)},
|
||
|
{ 67, 1, offsetof(struct richtek_spm_classdev, monitor_on)},
|
||
|
{ 4004, 1, offsetof(struct richtek_spm_classdev, tmaxcnt)},
|
||
|
{ 4006, 1, offsetof(struct richtek_spm_classdev, xmaxcnt)},
|
||
|
};
|
||
|
|
||
|
static const struct spm_param_item ampoff_rparam_items[] = {
|
||
|
{ 4003, 1, offsetof(struct richtek_spm_classdev, tmax)},
|
||
|
{ 4004, 1, offsetof(struct richtek_spm_classdev, tmaxcnt)},
|
||
|
{ 4005, 1, offsetof(struct richtek_spm_classdev, xmax)},
|
||
|
{ 4006, 1, offsetof(struct richtek_spm_classdev, xmaxcnt)},
|
||
|
};
|
||
|
|
||
|
static const struct spm_param_item calib_wswitch_items[] = {
|
||
|
{ 4001, 1, offsetof(struct richtek_spm_classdev, calib_enable)},
|
||
|
};
|
||
|
|
||
|
static const struct spm_param_item vali_wswitch_items[] = {
|
||
|
{ 4008, 1, offsetof(struct richtek_spm_classdev, vali_enable)},
|
||
|
};
|
||
|
|
||
|
static const struct spm_param_item calib_rstatus_items[] = {
|
||
|
{ 4000, 1, offsetof(struct richtek_spm_classdev, calib_status)},
|
||
|
{ 56, 1, offsetof(struct richtek_spm_classdev, rspk)},
|
||
|
};
|
||
|
|
||
|
static const struct spm_param_item vali_rstatus_items[] = {
|
||
|
{ 4009, 1, offsetof(struct richtek_spm_classdev, vali_status)},
|
||
|
{ 108, 1, offsetof(struct richtek_spm_classdev, vali_real_power)},
|
||
|
};
|
||
|
|
||
|
static ssize_t richtek_spm_dev_attr_show(struct device *,
|
||
|
struct device_attribute *, char *);
|
||
|
static ssize_t richtek_spm_dev_attr_store(struct device *,
|
||
|
struct device_attribute *,
|
||
|
const char *, size_t);
|
||
|
static struct device_attribute richtek_spm_dev_attrs[] = {
|
||
|
__ATTR(rspk, 0444,
|
||
|
richtek_spm_dev_attr_show, richtek_spm_dev_attr_store),
|
||
|
__ATTR(index, 0644,
|
||
|
richtek_spm_dev_attr_show, richtek_spm_dev_attr_store),
|
||
|
__ATTR(spkidx, 0444,
|
||
|
richtek_spm_dev_attr_show, richtek_spm_dev_attr_store),
|
||
|
__ATTR(params, 0644,
|
||
|
richtek_spm_dev_attr_show, richtek_spm_dev_attr_store),
|
||
|
__ATTR(param_items, 0644,
|
||
|
richtek_spm_dev_attr_show, richtek_spm_dev_attr_store),
|
||
|
__ATTR(ipc_go, 0220,
|
||
|
richtek_spm_dev_attr_show, richtek_spm_dev_attr_store),
|
||
|
__ATTR(tmax, 0444,
|
||
|
richtek_spm_dev_attr_show, richtek_spm_dev_attr_store),
|
||
|
__ATTR(tmaxcnt, 0444,
|
||
|
richtek_spm_dev_attr_show, richtek_spm_dev_attr_store),
|
||
|
__ATTR(xmax, 0444,
|
||
|
richtek_spm_dev_attr_show, richtek_spm_dev_attr_store),
|
||
|
__ATTR(xmaxcnt, 0444,
|
||
|
richtek_spm_dev_attr_show, richtek_spm_dev_attr_store),
|
||
|
__ATTR(tmaxkeep, 0444,
|
||
|
richtek_spm_dev_attr_show, richtek_spm_dev_attr_store),
|
||
|
__ATTR(xmaxkeep, 0444,
|
||
|
richtek_spm_dev_attr_show, richtek_spm_dev_attr_store),
|
||
|
__ATTR(real_power, 0444,
|
||
|
richtek_spm_dev_attr_show, NULL),
|
||
|
__ATTR_NULL,
|
||
|
};
|
||
|
|
||
|
static struct attribute * richtek_spm_classdev_attrs[] = {
|
||
|
&richtek_spm_dev_attrs[0].attr,
|
||
|
&richtek_spm_dev_attrs[1].attr,
|
||
|
&richtek_spm_dev_attrs[2].attr,
|
||
|
&richtek_spm_dev_attrs[3].attr,
|
||
|
&richtek_spm_dev_attrs[4].attr,
|
||
|
&richtek_spm_dev_attrs[5].attr,
|
||
|
&richtek_spm_dev_attrs[6].attr,
|
||
|
&richtek_spm_dev_attrs[7].attr,
|
||
|
&richtek_spm_dev_attrs[8].attr,
|
||
|
&richtek_spm_dev_attrs[9].attr,
|
||
|
&richtek_spm_dev_attrs[10].attr,
|
||
|
&richtek_spm_dev_attrs[11].attr,
|
||
|
&richtek_spm_dev_attrs[12].attr,
|
||
|
NULL,
|
||
|
};
|
||
|
|
||
|
static const struct attribute_group richtek_spm_classdev_group = {
|
||
|
.attrs = richtek_spm_classdev_attrs,
|
||
|
};
|
||
|
|
||
|
static const struct attribute_group *richtek_spm_classdev_groups[] = {
|
||
|
&richtek_spm_classdev_group,
|
||
|
NULL,
|
||
|
};
|
||
|
|
||
|
static int rt_spm_monitor_convert(int id, char *buf, int size, s32 val)
|
||
|
{
|
||
|
int ret;
|
||
|
|
||
|
if (!buf || size == 0)
|
||
|
return -EINVAL;
|
||
|
switch (id) {
|
||
|
case RICHTEK_SPM_DEV_TMAX:
|
||
|
/* Fall Through */
|
||
|
case RICHTEK_SPM_DEV_TMAXKEEP:
|
||
|
ret = scnprintf(buf, PAGE_SIZE, "%d\n", val / 1000);
|
||
|
break;
|
||
|
case RICHTEK_SPM_DEV_TMAXCNT:
|
||
|
case RICHTEK_SPM_DEV_XMAX:
|
||
|
case RICHTEK_SPM_DEV_XMAXKEEP:
|
||
|
/* Fall Through */
|
||
|
case RICHTEK_SPM_DEV_XMAXCNT:
|
||
|
ret = scnprintf(buf, PAGE_SIZE, "%d\n", val);
|
||
|
break;
|
||
|
default:
|
||
|
ret = -EINVAL;
|
||
|
break;
|
||
|
}
|
||
|
return ret;
|
||
|
}
|
||
|
|
||
|
static ssize_t richtek_spm_dev_attr_show(struct device *dev,
|
||
|
struct device_attribute *attr, char *buf)
|
||
|
{
|
||
|
struct richtek_spm_classdev *rdc = dev_get_drvdata(dev);
|
||
|
const ptrdiff_t offset = attr - richtek_spm_dev_attrs;
|
||
|
const struct spm_param_item *pitem;
|
||
|
int i, ret = -EINVAL;
|
||
|
|
||
|
mutex_lock(&rdc->var_lock);
|
||
|
switch (offset) {
|
||
|
case RICHTEK_SPM_DEV_RSPK:
|
||
|
ret = scnprintf(buf, PAGE_SIZE, "%u\n", rdc->rspk);
|
||
|
break;
|
||
|
case RICHTEK_SPM_DEV_INDEX:
|
||
|
ret = scnprintf(buf, PAGE_SIZE, "%u\n", rdc->ipc_msg.index);
|
||
|
break;
|
||
|
case RICHTEK_SPM_DEV_SPKIDX:
|
||
|
ret = scnprintf(buf, PAGE_SIZE, "%u\n", rdc->spkidx);
|
||
|
break;
|
||
|
case RICHTEK_SPM_DEV_PARAMS:
|
||
|
ret = 0;
|
||
|
for (i = 0; i < rdc->ipc_msg.param_items; i++) {
|
||
|
ret += scnprintf(buf + ret, PAGE_SIZE - ret,
|
||
|
(i > 0) ? ",%d" : "%d",
|
||
|
rdc->ipc_msg.params[i]);
|
||
|
if (ret < 0)
|
||
|
break;
|
||
|
}
|
||
|
ret += scnprintf(buf + ret, PAGE_SIZE - ret, "\n");
|
||
|
break;
|
||
|
case RICHTEK_SPM_DEV_PARAM_ITEMS:
|
||
|
ret = scnprintf(buf,
|
||
|
PAGE_SIZE, "%d\n", rdc->ipc_msg.param_items);
|
||
|
break;
|
||
|
case RICHTEK_SPM_DEV_TMAX:
|
||
|
case RICHTEK_SPM_DEV_TMAXCNT:
|
||
|
case RICHTEK_SPM_DEV_XMAX:
|
||
|
/* Fall Through */
|
||
|
case RICHTEK_SPM_DEV_XMAXCNT:
|
||
|
pitem = ampoff_rparam_items + (offset - RICHTEK_SPM_DEV_TMAX);
|
||
|
ret = rt_spm_monitor_convert(offset, buf, PAGE_SIZE,
|
||
|
*(s32 *)((void *)rdc + pitem->offset));
|
||
|
/* after get, reset to 0 */
|
||
|
*(s32 *)((void *)rdc + pitem->offset) = 0;
|
||
|
break;
|
||
|
case RICHTEK_SPM_DEV_TMAXKEEP:
|
||
|
ret = rt_spm_monitor_convert(offset, buf, PAGE_SIZE,
|
||
|
rdc->boot_on_tmax);
|
||
|
break;
|
||
|
case RICHTEK_SPM_DEV_XMAXKEEP:
|
||
|
ret = rt_spm_monitor_convert(offset, buf, PAGE_SIZE,
|
||
|
rdc->boot_on_xmax);
|
||
|
break;
|
||
|
case RICHTEK_SPM_DEV_VVALIDATION_REAL_POWER:
|
||
|
ret = scnprintf(buf, PAGE_SIZE, "%d.%dW\n",
|
||
|
rdc->vali_real_power / 1000, rdc->vali_real_power % 1000);
|
||
|
break;
|
||
|
case RICHTEK_SPM_DEV_IPC_GO:
|
||
|
default:
|
||
|
break;
|
||
|
}
|
||
|
mutex_unlock(&rdc->var_lock);
|
||
|
return ret;
|
||
|
}
|
||
|
|
||
|
static int richtek_spm_dev_parsing_params(const char *buf,
|
||
|
struct richtek_spm_classdev *rdc)
|
||
|
{
|
||
|
const char *delim = ",/ \n";
|
||
|
char *buf_cpy, *buf_cpy2, *tmp;
|
||
|
int i = 0, ret = 0;
|
||
|
|
||
|
buf_cpy = buf_cpy2 = kstrdup(buf, GFP_KERNEL);
|
||
|
while ((tmp = strsep(&buf_cpy2, delim)) != NULL) {
|
||
|
if (!strcmp(tmp, ""))
|
||
|
continue;
|
||
|
ret = kstrtos32(tmp, 0, rdc->ipc_msg.params + (i++));
|
||
|
if (i >= RICHTEK_SPM_MAX_PARAM_ITEMS || ret < 0)
|
||
|
break;
|
||
|
}
|
||
|
kfree(buf_cpy);
|
||
|
return (ret < 0) ? ret : i;
|
||
|
}
|
||
|
|
||
|
static inline void richtek_spm_dev_erase_all(struct richtek_spm_classdev *rdc)
|
||
|
{
|
||
|
dev_dbg(rdc->dev, "%s\n", __func__);
|
||
|
memset(&rdc->ipc_msg, 0, sizeof(rdc->ipc_msg));
|
||
|
}
|
||
|
|
||
|
static int richtek_spm_dev_execute_ipc(struct richtek_spm_classdev *rdc)
|
||
|
{
|
||
|
int ret = 0;
|
||
|
|
||
|
if (!rdc->ipc_msg.param_items)
|
||
|
return -EINVAL;
|
||
|
/* xlate from kernel intf to fw_ipc */
|
||
|
/* 1) items to size. 2) cmd index */
|
||
|
rdc->ipc_msg.param_items *= 4;
|
||
|
rdc->ipc_msg.cmd -= RICHTEK_SPM_MAGIC;
|
||
|
rdc->ipc_msg.spkid = rdc->spkidx;
|
||
|
dev_dbg(rdc->dev, "cmd = %u, index = %u, spkid = %u\n",
|
||
|
rdc->ipc_msg.cmd, rdc->ipc_msg.index, rdc->ipc_msg.spkid);
|
||
|
dev_dbg(rdc->dev, "param_size = %d, param[0] = %d\n",
|
||
|
rdc->ipc_msg.param_items, rdc->ipc_msg.params[0]);
|
||
|
switch (rdc->ipc_msg.cmd) {
|
||
|
case CMD_READ:
|
||
|
dev_info(rdc->dev, "will execute read\n");
|
||
|
/* Add richtek_spm_read */
|
||
|
ret = richtek_spm_read(&rdc->ipc_msg, sizeof(rdc->ipc_msg));
|
||
|
if (ret < 0)
|
||
|
dev_err(rdc->dev, "spm_read fail\n");
|
||
|
break;
|
||
|
case CMD_WRITE:
|
||
|
dev_info(rdc->dev, "will execute write\n");
|
||
|
/* Add richtek_spm_write */
|
||
|
ret = richtek_spm_write(&rdc->ipc_msg, sizeof(rdc->ipc_msg));
|
||
|
if (ret < 0)
|
||
|
dev_err(rdc->dev, "spm_write fail\n");
|
||
|
break;
|
||
|
default:
|
||
|
ret = -EINVAL;
|
||
|
break;
|
||
|
}
|
||
|
/* xlate back from fw_ipc to kernel intf */
|
||
|
/* 1) cmd index. 2) size to items */
|
||
|
rdc->ipc_msg.cmd += RICHTEK_SPM_MAGIC;
|
||
|
rdc->ipc_msg.param_items /= 4 ;
|
||
|
return (ret < 0) ? ret : 0;
|
||
|
}
|
||
|
|
||
|
static ssize_t richtek_spm_dev_attr_store(struct device *dev,
|
||
|
struct device_attribute *attr,
|
||
|
const char *buf, size_t cnt)
|
||
|
{
|
||
|
struct richtek_spm_classdev *rdc = dev_get_drvdata(dev);
|
||
|
const ptrdiff_t offset = attr - richtek_spm_dev_attrs;
|
||
|
int ret = 0;
|
||
|
|
||
|
mutex_lock(&rdc->var_lock);
|
||
|
switch (offset) {
|
||
|
case RICHTEK_SPM_DEV_INDEX:
|
||
|
ret = kstrtou32(buf, 0, &rdc->ipc_msg.index);
|
||
|
if (ret < 0)
|
||
|
goto store_fail;
|
||
|
break;
|
||
|
case RICHTEK_SPM_DEV_PARAMS:
|
||
|
ret = richtek_spm_dev_parsing_params(buf, rdc);
|
||
|
if (ret < 0)
|
||
|
goto store_fail;
|
||
|
if (ret != rdc->ipc_msg.param_items) {
|
||
|
ret = -EINVAL;
|
||
|
goto store_fail;
|
||
|
}
|
||
|
break;
|
||
|
case RICHTEK_SPM_DEV_PARAM_ITEMS:
|
||
|
ret = kstrtos32(buf, 0, &rdc->ipc_msg.param_items);
|
||
|
if (ret < 0)
|
||
|
goto store_fail;
|
||
|
break;
|
||
|
case RICHTEK_SPM_DEV_IPC_GO:
|
||
|
ret = kstrtou32(buf, 0, &rdc->ipc_msg.cmd);
|
||
|
if (ret < 0)
|
||
|
goto store_fail;
|
||
|
ret = richtek_spm_dev_execute_ipc(rdc);
|
||
|
if (ret < 0)
|
||
|
goto store_fail;
|
||
|
break;
|
||
|
case RICHTEK_SPM_DEV_RSPK:
|
||
|
case RICHTEK_SPM_DEV_SPKIDX:
|
||
|
case RICHTEK_SPM_DEV_TMAX:
|
||
|
case RICHTEK_SPM_DEV_TMAXCNT:
|
||
|
case RICHTEK_SPM_DEV_XMAX:
|
||
|
case RICHTEK_SPM_DEV_XMAXCNT:
|
||
|
default:
|
||
|
break;
|
||
|
}
|
||
|
mutex_unlock(&rdc->var_lock);
|
||
|
return cnt;
|
||
|
store_fail:
|
||
|
richtek_spm_dev_erase_all(rdc);
|
||
|
mutex_unlock(&rdc->var_lock);
|
||
|
return ret;
|
||
|
}
|
||
|
|
||
|
static int rt_spm_classdev_rw_param(struct richtek_spm_classdev *rdc,
|
||
|
const struct spm_param_item *pitem,
|
||
|
bool read)
|
||
|
{
|
||
|
struct richtek_spm_ipc_msg *pipc_msg = &rdc->ipc_msg;
|
||
|
int ret;
|
||
|
|
||
|
if (!rdc || !pitem)
|
||
|
return -EINVAL;
|
||
|
memset(pipc_msg, 0, sizeof(*pipc_msg));
|
||
|
pipc_msg->cmd = RICHTEK_SPM_MAGIC + (read ? CMD_READ : CMD_WRITE);
|
||
|
pipc_msg->index = pitem->index;
|
||
|
pipc_msg->param_items = pitem->param_items;
|
||
|
if (!read) {
|
||
|
memcpy(pipc_msg->params, (void *)rdc + pitem->offset,
|
||
|
sizeof(s32) * pitem->param_items);
|
||
|
}
|
||
|
ret = richtek_spm_dev_execute_ipc(rdc);
|
||
|
if (ret < 0) {
|
||
|
dev_err(rdc->dev, "%s: execute ipc fail\n", __func__);
|
||
|
goto out_rw_param;
|
||
|
}
|
||
|
if (read) {
|
||
|
memcpy((void *)rdc + pitem->offset,
|
||
|
pipc_msg->params, sizeof(s32) * pitem->param_items);
|
||
|
}
|
||
|
out_rw_param:
|
||
|
richtek_spm_dev_erase_all(rdc);
|
||
|
return (ret < 0) ? ret : 0;
|
||
|
}
|
||
|
|
||
|
static ssize_t richtek_spm_class_attr_show(struct class *,
|
||
|
struct class_attribute *, char *);
|
||
|
static ssize_t richtek_spm_class_attr_store(struct class *,
|
||
|
struct class_attribute *, const char *, size_t);
|
||
|
static const struct class_attribute richtek_spm_class_attrs[] = {
|
||
|
__ATTR(trigger, 0220, richtek_spm_class_attr_show,
|
||
|
richtek_spm_class_attr_store),
|
||
|
__ATTR(status, 0664, richtek_spm_class_attr_show,
|
||
|
richtek_spm_class_attr_store),
|
||
|
__ATTR(internal_status, 0444, richtek_spm_class_attr_show,
|
||
|
richtek_spm_class_attr_store),
|
||
|
__ATTR_NULL,
|
||
|
};
|
||
|
|
||
|
enum {
|
||
|
RICHTEK_SPM_CLASS_TRIGGER = 0,
|
||
|
RICHTEK_SPM_CLASS_STATUS, /* this is for Samsung Calibration */
|
||
|
RICHTEK_SPM_CLASS_INTERNAL_STATUS,
|
||
|
RICHTEK_SPM_CLASS_MAX,
|
||
|
};
|
||
|
|
||
|
static ssize_t richtek_spm_class_attr_show(struct class *cls,
|
||
|
struct class_attribute *attr, char *buf)
|
||
|
{
|
||
|
const ptrdiff_t offset = attr - richtek_spm_class_attrs;
|
||
|
int ret = 0;
|
||
|
|
||
|
switch (offset) {
|
||
|
case RICHTEK_SPM_CLASS_INTERNAL_STATUS:
|
||
|
ret = scnprintf(buf, PAGE_SIZE, "%d\n", trig_status);
|
||
|
break;
|
||
|
case RICHTEK_SPM_CLASS_STATUS:
|
||
|
ret = scnprintf(buf, PAGE_SIZE,
|
||
|
"%s\n", cali_status ? "Enabled" : "Disable");
|
||
|
break;
|
||
|
case RICHTEK_SPM_CLASS_TRIGGER:
|
||
|
default:
|
||
|
return -EINVAL;
|
||
|
}
|
||
|
return ret;
|
||
|
}
|
||
|
|
||
|
static int fs_read_file(char *filename, char *data, size_t size)
|
||
|
{
|
||
|
struct file *filp;
|
||
|
mm_segment_t old_fs = get_fs();
|
||
|
int flags = O_RDONLY;
|
||
|
int ret;
|
||
|
|
||
|
set_fs(KERNEL_DS);
|
||
|
filp = filp_open(filename, flags, 0444);
|
||
|
if (IS_ERR(filp)) {
|
||
|
pr_err("[rt_spm] there is no spm_cal file\n");
|
||
|
set_fs(old_fs);
|
||
|
return -EEXIST;
|
||
|
}
|
||
|
ret = vfs_read(filp, data, size, &filp->f_pos);
|
||
|
if (ret < 0) {
|
||
|
pr_err("[rt_spm] can't read spm calibration value to file\n");
|
||
|
ret = -EIO;
|
||
|
}
|
||
|
filp_close(filp, current->files);
|
||
|
set_fs(old_fs);
|
||
|
return ret;
|
||
|
}
|
||
|
|
||
|
static int fs_write_file(char *filename, const char *data, size_t size)
|
||
|
{
|
||
|
struct file *filp;
|
||
|
mm_segment_t old_fs = get_fs();
|
||
|
int flags = O_CREAT | O_TRUNC | O_WRONLY;
|
||
|
int ret;
|
||
|
|
||
|
set_fs(KERNEL_DS);
|
||
|
filp = filp_open(filename, flags, 0444);
|
||
|
if (IS_ERR(filp)) {
|
||
|
pr_err("[rt_spm] Can't open calibration file\n");
|
||
|
set_fs(old_fs);
|
||
|
ret = PTR_ERR(filp);
|
||
|
return ret;
|
||
|
}
|
||
|
ret = vfs_write(filp, data, size, &filp->f_pos);
|
||
|
if (ret != size) {
|
||
|
pr_err("%s: can't write spm calibration value to file\n",
|
||
|
__func__);
|
||
|
ret = -EIO;
|
||
|
}
|
||
|
filp_close(filp, current->files);
|
||
|
set_fs(old_fs);
|
||
|
|
||
|
return ret;
|
||
|
}
|
||
|
|
||
|
enum {
|
||
|
TRIGGER_CMD_READ = 0,
|
||
|
TRIGGER_CMD_WRITE,
|
||
|
TRIGGER_CMD_CALIBRATION,
|
||
|
TRIGGER_CMD_VVALIDATION,
|
||
|
TRIGGER_CMD_S_CALIBRATION, /* only calibration but don't write to efs */
|
||
|
TRIGGER_CMD_MAX,
|
||
|
};
|
||
|
|
||
|
static int rt_spm_vali_threadfn(void *data)
|
||
|
{
|
||
|
struct richtek_spm_classdev *rdc = data;
|
||
|
const struct spm_param_item *pitem;
|
||
|
unsigned int i;
|
||
|
int max_try = 5, ret;
|
||
|
|
||
|
dev_dbg(rdc->dev, "%s: start\n", __func__);
|
||
|
mutex_lock(&rdc->var_lock);
|
||
|
/* vali enable write */
|
||
|
rdc->vali_enable = 1;
|
||
|
for (i = 0; i < ARRAY_SIZE(vali_wswitch_items); i++) {
|
||
|
pitem = vali_wswitch_items + i;
|
||
|
ret = rt_spm_classdev_rw_param(rdc, pitem, false);
|
||
|
if (ret < 0) {
|
||
|
dev_err(rdc->dev, "vali start param [%d] fail\n", i);
|
||
|
goto out_threadfn;
|
||
|
}
|
||
|
}
|
||
|
msleep(300);
|
||
|
while (max_try--) {
|
||
|
for (i = 0; i < ARRAY_SIZE(vali_rstatus_items); i++) {
|
||
|
pitem = vali_rstatus_items + i;
|
||
|
ret = rt_spm_classdev_rw_param(rdc, pitem, true);
|
||
|
if (ret < 0) {
|
||
|
dev_err(rdc->dev, "state param [%d] fail\n", i);
|
||
|
goto out_threadfn;
|
||
|
}
|
||
|
}
|
||
|
if (rdc->vali_status < 0) {
|
||
|
ret = -EINVAL;
|
||
|
break;
|
||
|
} else if (rdc->vali_status == 2) {
|
||
|
dev_dbg(rdc->dev, "vvalidation ending\n");
|
||
|
ret = 0;
|
||
|
break;
|
||
|
}
|
||
|
if (max_try == 0) {
|
||
|
ret = -EINVAL;
|
||
|
break;
|
||
|
}
|
||
|
msleep(100);
|
||
|
}
|
||
|
out_threadfn:
|
||
|
rdc->vali_enable = 0;
|
||
|
/* vali disable write */
|
||
|
for (i = 0; i < ARRAY_SIZE(vali_wswitch_items); i++) {
|
||
|
pitem = vali_wswitch_items + i;
|
||
|
rt_spm_classdev_rw_param(rdc, pitem, false);
|
||
|
}
|
||
|
rdc->vali_status = ret;
|
||
|
mutex_unlock(&rdc->var_lock);
|
||
|
complete(&rdc->trig_complete);
|
||
|
dev_dbg(rdc->dev, "%s: end\n", __func__);
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
static int rt_spm_calib_threadfn(void *data)
|
||
|
{
|
||
|
struct richtek_spm_classdev *rdc = data;
|
||
|
const struct spm_param_item *pitem;
|
||
|
unsigned int i;
|
||
|
int max_try = 5, ret;
|
||
|
|
||
|
dev_dbg(rdc->dev, "%s: start\n", __func__);
|
||
|
mutex_lock(&rdc->var_lock);
|
||
|
/* calib enable write */
|
||
|
rdc->calib_enable = 1;
|
||
|
for (i = 0; i < ARRAY_SIZE(calib_wswitch_items); i++) {
|
||
|
pitem = calib_wswitch_items + i;
|
||
|
ret = rt_spm_classdev_rw_param(rdc, pitem, false);
|
||
|
if (ret < 0) {
|
||
|
dev_err(rdc->dev, "calib start param [%d] fail\n", i);
|
||
|
goto out_threadfn;
|
||
|
}
|
||
|
}
|
||
|
msleep(300);
|
||
|
while (max_try--) {
|
||
|
for (i = 0; i < ARRAY_SIZE(calib_rstatus_items); i++) {
|
||
|
pitem = calib_rstatus_items + i;
|
||
|
ret = rt_spm_classdev_rw_param(rdc, pitem, true);
|
||
|
if (ret < 0) {
|
||
|
dev_err(rdc->dev, "state param [%d] fail\n", i);
|
||
|
goto out_threadfn;
|
||
|
}
|
||
|
}
|
||
|
if (rdc->calib_status < 0) {
|
||
|
ret = -EINVAL;
|
||
|
break;
|
||
|
} else if (rdc->calib_status == 2) {
|
||
|
dev_dbg(rdc->dev, "calib ending\n");
|
||
|
ret = 0;
|
||
|
break;
|
||
|
}
|
||
|
if (max_try == 0) {
|
||
|
ret = -EINVAL;
|
||
|
break;
|
||
|
}
|
||
|
msleep(100);
|
||
|
}
|
||
|
out_threadfn:
|
||
|
rdc->calib_enable = 0;
|
||
|
/* calib disable write */
|
||
|
for (i = 0; i < ARRAY_SIZE(calib_wswitch_items); i++) {
|
||
|
pitem = calib_wswitch_items + i;
|
||
|
rt_spm_classdev_rw_param(rdc, pitem, false);
|
||
|
}
|
||
|
rdc->calib_status = ret;
|
||
|
mutex_unlock(&rdc->var_lock);
|
||
|
complete(&rdc->trig_complete);
|
||
|
dev_dbg(rdc->dev, "%s: end\n", __func__);
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
static int rt_spm_cls_trigger_write(struct device *dev, void *data)
|
||
|
{
|
||
|
struct richtek_spm_classdev *rdc = dev_get_drvdata(dev);
|
||
|
char rspk_dev_path[256];
|
||
|
char rspk_nstr[20];
|
||
|
u32 cmd = *(u32 *)data;
|
||
|
int ret = 0;
|
||
|
|
||
|
memset(rspk_nstr, 0, sizeof(rspk_nstr));
|
||
|
ret = scnprintf(rspk_dev_path, sizeof(rspk_dev_path),
|
||
|
"%s%u/rdc_cal", RICHTEK_SPM_RSPK_PATH, rdc->spkidx);
|
||
|
if (ret < 0)
|
||
|
goto out_trigger_write;
|
||
|
switch (cmd) {
|
||
|
case TRIGGER_CMD_READ:
|
||
|
ret = fs_read_file(rspk_dev_path, rspk_nstr, sizeof(rspk_nstr));
|
||
|
if (ret < 0)
|
||
|
goto out_trigger_write;
|
||
|
mutex_lock(&rdc->var_lock);
|
||
|
ret = kstrtou32(rspk_nstr, 0, &rdc->rspk);
|
||
|
mutex_unlock(&rdc->var_lock);
|
||
|
break;
|
||
|
case TRIGGER_CMD_WRITE:
|
||
|
mutex_lock(&rdc->var_lock);
|
||
|
ret = scnprintf(rspk_nstr, sizeof(rspk_nstr), "%u\n", rdc->rspk);
|
||
|
mutex_unlock(&rdc->var_lock);
|
||
|
if (ret < 0)
|
||
|
goto out_trigger_write;
|
||
|
ret = fs_write_file(rspk_dev_path, rspk_nstr, ret);
|
||
|
break;
|
||
|
case TRIGGER_CMD_S_CALIBRATION:
|
||
|
reinit_completion(&rdc->trig_complete);
|
||
|
rdc->trig_task = kthread_run(rt_spm_calib_threadfn,
|
||
|
rdc, "calib_thread.%s",
|
||
|
dev_name(rdc->dev));
|
||
|
if (IS_ERR(rdc->trig_task)) {
|
||
|
ret = PTR_ERR(rdc->trig_task);
|
||
|
goto out_trigger_write;
|
||
|
}
|
||
|
dev_dbg(rdc->dev, "wait thread\n");
|
||
|
ret = wait_for_completion_interruptible(&rdc->trig_complete);
|
||
|
if (ret < 0) {
|
||
|
kthread_stop(rdc->trig_task);
|
||
|
goto out_trigger_write;
|
||
|
}
|
||
|
if (rdc->calib_status < 0) {
|
||
|
ret = rdc->calib_status;
|
||
|
goto out_trigger_write;
|
||
|
}
|
||
|
dev_dbg(rdc->dev, "trigger s calib write\n");
|
||
|
break;
|
||
|
case TRIGGER_CMD_CALIBRATION:
|
||
|
reinit_completion(&rdc->trig_complete);
|
||
|
rdc->trig_task = kthread_run(rt_spm_calib_threadfn,
|
||
|
rdc, "calib_thread.%s",
|
||
|
dev_name(rdc->dev));
|
||
|
if (IS_ERR(rdc->trig_task)) {
|
||
|
ret = PTR_ERR(rdc->trig_task);
|
||
|
goto out_trigger_write;
|
||
|
}
|
||
|
dev_dbg(rdc->dev, "wait thread\n");
|
||
|
ret = wait_for_completion_interruptible(&rdc->trig_complete);
|
||
|
if (ret < 0) {
|
||
|
kthread_stop(rdc->trig_task);
|
||
|
goto out_trigger_write;
|
||
|
}
|
||
|
if (rdc->calib_status < 0) {
|
||
|
ret = rdc->calib_status;
|
||
|
goto out_trigger_write;
|
||
|
}
|
||
|
dev_dbg(rdc->dev, "trigger calib write\n");
|
||
|
cmd = TRIGGER_CMD_WRITE;
|
||
|
ret = rt_spm_cls_trigger_write(rdc->dev, &cmd);
|
||
|
break;
|
||
|
case TRIGGER_CMD_VVALIDATION:
|
||
|
reinit_completion(&rdc->trig_complete);
|
||
|
rdc->trig_task = kthread_run(rt_spm_vali_threadfn,
|
||
|
rdc, "vali_thread.%s",
|
||
|
dev_name(rdc->dev));
|
||
|
if (IS_ERR(rdc->trig_task)) {
|
||
|
ret = PTR_ERR(rdc->trig_task);
|
||
|
goto out_trigger_write;
|
||
|
}
|
||
|
dev_dbg(rdc->dev, "wait thread\n");
|
||
|
ret = wait_for_completion_interruptible(&rdc->trig_complete);
|
||
|
if (ret < 0) {
|
||
|
kthread_stop(rdc->trig_task);
|
||
|
goto out_trigger_write;
|
||
|
}
|
||
|
if (rdc->vali_status < 0) {
|
||
|
ret = rdc->vali_status;
|
||
|
goto out_trigger_write;
|
||
|
}
|
||
|
dev_dbg(rdc->dev, "trigger vali wtire\n");
|
||
|
break;
|
||
|
default:
|
||
|
ret = -EINVAL;
|
||
|
break;
|
||
|
}
|
||
|
out_trigger_write:
|
||
|
return (ret < 0) ? ret : 0;
|
||
|
}
|
||
|
|
||
|
static ssize_t richtek_spm_class_attr_store(struct class *cls,
|
||
|
struct class_attribute *attr,
|
||
|
const char *buf, size_t cnt)
|
||
|
{
|
||
|
const ptrdiff_t offset = attr - richtek_spm_class_attrs;
|
||
|
u32 tmp = 0;
|
||
|
int ret = 0;
|
||
|
|
||
|
switch (offset) {
|
||
|
case RICHTEK_SPM_CLASS_TRIGGER:
|
||
|
ret = kstrtou32(buf, 0, &tmp);
|
||
|
if (ret < 0)
|
||
|
return ret;
|
||
|
tmp -= RICHTEK_SPM_MAGIC;
|
||
|
pr_info("[richtek_spm] start trigger\n");
|
||
|
ret = class_for_each_device(cls, NULL, &tmp,
|
||
|
rt_spm_cls_trigger_write);
|
||
|
trig_status = ret;
|
||
|
if (ret < 0)
|
||
|
return ret;
|
||
|
break;
|
||
|
case RICHTEK_SPM_CLASS_STATUS:
|
||
|
ret = kstrtou32(buf, 0, &tmp);
|
||
|
if (ret < 0)
|
||
|
return ret;
|
||
|
if (tmp == 1) {
|
||
|
cali_status = 1;
|
||
|
tmp = TRIGGER_CMD_S_CALIBRATION;
|
||
|
ret = class_for_each_device(cls, NULL, &tmp,
|
||
|
rt_spm_cls_trigger_write);
|
||
|
cali_status = 0;
|
||
|
if (ret < 0)
|
||
|
return ret;
|
||
|
}
|
||
|
break;
|
||
|
case RICHTEK_SPM_CLASS_INTERNAL_STATUS:
|
||
|
default:
|
||
|
return -EINVAL;
|
||
|
}
|
||
|
return cnt;
|
||
|
}
|
||
|
|
||
|
#ifdef CONFIG_PM_SLEEP
|
||
|
static int richtek_spm_classdev_suspend(struct device *dev)
|
||
|
{
|
||
|
struct richtek_spm_classdev *rdc = dev_get_drvdata(dev);
|
||
|
|
||
|
return (rdc->ops && rdc->ops->suspend) ? rdc->ops->suspend(rdc) : 0;
|
||
|
}
|
||
|
|
||
|
static int richtek_spm_classdev_resume(struct device *dev)
|
||
|
{
|
||
|
struct richtek_spm_classdev *rdc = dev_get_drvdata(dev);
|
||
|
|
||
|
return (rdc->ops && rdc->ops->resume) ? rdc->ops->resume(rdc) : 0;
|
||
|
}
|
||
|
#endif /* CONFIG_PM_SLEEP */
|
||
|
|
||
|
static SIMPLE_DEV_PM_OPS(richtek_spm_class_pm_ops,
|
||
|
richtek_spm_classdev_suspend, richtek_spm_classdev_resume);
|
||
|
|
||
|
int richtek_spm_classdev_register(struct device *parent,
|
||
|
struct richtek_spm_classdev *rdc)
|
||
|
{
|
||
|
static u32 spk_index = 0;
|
||
|
|
||
|
if (rdc == NULL)
|
||
|
return -EINVAL;
|
||
|
if (parent == NULL && rdc->name == NULL) {
|
||
|
pr_err("[richtek_spm] no name can be speficied\n");
|
||
|
return -EINVAL;
|
||
|
}
|
||
|
if (rdc->name == NULL) {
|
||
|
rdc->name = devm_kasprintf(parent,
|
||
|
GFP_KERNEL, "rt_amp%d", spk_index);
|
||
|
}
|
||
|
rdc->dev = device_create_with_groups(richtek_spm_class, parent, 0,
|
||
|
rdc, rdc->groups, "%s",
|
||
|
rdc->name);
|
||
|
if (IS_ERR(rdc->dev))
|
||
|
return PTR_ERR(rdc->dev);
|
||
|
dev_set_drvdata(rdc->dev, rdc);
|
||
|
/* init */
|
||
|
init_completion(&rdc->trig_complete);
|
||
|
mutex_init(&rdc->var_lock);
|
||
|
memset(&rdc->ipc_msg, 0, sizeof(rdc->ipc_msg));
|
||
|
rdc->spkidx = spk_index++;
|
||
|
return 0;
|
||
|
}
|
||
|
EXPORT_SYMBOL_GPL(richtek_spm_classdev_register);
|
||
|
|
||
|
void richtek_spm_classdev_unregister(struct richtek_spm_classdev *rdc)
|
||
|
{
|
||
|
mutex_destroy(&rdc->var_lock);
|
||
|
device_unregister(rdc->dev);
|
||
|
}
|
||
|
EXPORT_SYMBOL_GPL(richtek_spm_classdev_unregister);
|
||
|
|
||
|
static void devm_richtek_spm_classdev_release(struct device *dev, void *res)
|
||
|
{
|
||
|
richtek_spm_classdev_unregister(*(struct richtek_spm_classdev **)res);
|
||
|
}
|
||
|
|
||
|
int devm_richtek_spm_classdev_register(struct device *parent,
|
||
|
struct richtek_spm_classdev *rdc)
|
||
|
{
|
||
|
struct richtek_spm_classdev **prdc;
|
||
|
int rc;
|
||
|
|
||
|
prdc = devres_alloc(devm_richtek_spm_classdev_release,
|
||
|
sizeof(*prdc), GFP_KERNEL);
|
||
|
if (!prdc)
|
||
|
return -ENOMEM;
|
||
|
rc = richtek_spm_classdev_register(parent, rdc);
|
||
|
if (rc < 0) {
|
||
|
devres_free(prdc);
|
||
|
return rc;
|
||
|
}
|
||
|
*prdc = rdc;
|
||
|
devres_add(parent, prdc);
|
||
|
return 0;
|
||
|
}
|
||
|
EXPORT_SYMBOL_GPL(devm_richtek_spm_classdev_register);
|
||
|
|
||
|
static int rt_spm_classdev_ampon(struct richtek_spm_classdev *rdc)
|
||
|
{
|
||
|
const struct spm_param_item *pitem;
|
||
|
u32 cmd = TRIGGER_CMD_READ;
|
||
|
unsigned int i;
|
||
|
int ret;
|
||
|
|
||
|
if (!rdc)
|
||
|
return -EINVAL;
|
||
|
/* trigger fs rspk read */
|
||
|
ret = rt_spm_cls_trigger_write(rdc->dev, &cmd);
|
||
|
if (ret < 0)
|
||
|
return ret;
|
||
|
mutex_lock(&rdc->var_lock);
|
||
|
rdc->monitor_on = 1;
|
||
|
for (i = 0; i < ARRAY_SIZE(ampon_wparam_items); i++) {
|
||
|
pitem = ampon_wparam_items + i;
|
||
|
ret = rt_spm_classdev_rw_param(rdc, pitem, false);
|
||
|
if (ret < 0) {
|
||
|
dev_err(rdc->dev, "ampon write param [%d] fail\n", i);
|
||
|
goto out_classdev_ampon;
|
||
|
}
|
||
|
}
|
||
|
out_classdev_ampon:
|
||
|
mutex_unlock(&rdc->var_lock);
|
||
|
return (ret < 0) ? ret : 0;
|
||
|
}
|
||
|
|
||
|
static int rt_spm_classdev_ampoff(struct richtek_spm_classdev *rdc)
|
||
|
{
|
||
|
const struct spm_param_item *pitem;
|
||
|
unsigned int i;
|
||
|
int ret;
|
||
|
|
||
|
if (!rdc)
|
||
|
return -EINVAL;
|
||
|
mutex_lock(&rdc->var_lock);
|
||
|
for (i = 0; i < ARRAY_SIZE(ampoff_rparam_items); i++) {
|
||
|
pitem = ampoff_rparam_items + i;
|
||
|
ret = rt_spm_classdev_rw_param(rdc, pitem, true);
|
||
|
if (ret < 0) {
|
||
|
dev_err(rdc->dev, "ampoff read param [%d]\n", i);
|
||
|
break;
|
||
|
}
|
||
|
}
|
||
|
rdc->boot_on_xmax = max(rdc->xmax, rdc->boot_on_xmax);
|
||
|
rdc->boot_on_tmax = max(rdc->tmax, rdc->boot_on_tmax);
|
||
|
mutex_unlock(&rdc->var_lock);
|
||
|
return (ret < 0) ? ret : 0;
|
||
|
}
|
||
|
|
||
|
static int rt_spm_cls_trigger_ampon(struct device *dev, void *data)
|
||
|
{
|
||
|
struct richtek_spm_classdev *rdc = dev_get_drvdata(dev);
|
||
|
|
||
|
return (rdc == data) ? rt_spm_classdev_ampon(rdc) : 0;
|
||
|
}
|
||
|
|
||
|
static int rt_spm_cls_trigger_ampoff(struct device *dev, void *data)
|
||
|
{
|
||
|
struct richtek_spm_classdev *rdc = dev_get_drvdata(dev);
|
||
|
|
||
|
return (rdc == data) ? rt_spm_classdev_ampoff(rdc) : 0;
|
||
|
}
|
||
|
|
||
|
int richtek_spm_classdev_trigger_ampon(struct richtek_spm_classdev *rdc)
|
||
|
{
|
||
|
return class_for_each_device(richtek_spm_class, NULL,
|
||
|
rdc, rt_spm_cls_trigger_ampon);
|
||
|
}
|
||
|
EXPORT_SYMBOL_GPL(richtek_spm_classdev_trigger_ampon);
|
||
|
|
||
|
int richtek_spm_classdev_trigger_ampoff(struct richtek_spm_classdev *rdc)
|
||
|
{
|
||
|
return class_for_each_device(richtek_spm_class, NULL,
|
||
|
rdc, rt_spm_cls_trigger_ampoff);
|
||
|
}
|
||
|
EXPORT_SYMBOL_GPL(richtek_spm_classdev_trigger_ampoff);
|
||
|
|
||
|
static int __init richtek_spm_init(void)
|
||
|
{
|
||
|
int i = 0, ret = 0;
|
||
|
|
||
|
richtek_spm_class = class_create(THIS_MODULE, "richtek_spm");
|
||
|
if (IS_ERR(richtek_spm_class))
|
||
|
return PTR_ERR(richtek_spm_class);
|
||
|
richtek_spm_class->pm = &richtek_spm_class_pm_ops;
|
||
|
for (i = 0; richtek_spm_class_attrs[i].attr.name; i++) {
|
||
|
ret = class_create_file(richtek_spm_class,
|
||
|
richtek_spm_class_attrs + i);
|
||
|
if (ret < 0)
|
||
|
goto out_cls_attr;
|
||
|
}
|
||
|
richtek_spm_class->dev_groups = richtek_spm_classdev_groups;
|
||
|
return 0;
|
||
|
out_cls_attr:
|
||
|
while (--i >= 0) {
|
||
|
class_remove_file(richtek_spm_class,
|
||
|
richtek_spm_class_attrs + i);
|
||
|
}
|
||
|
class_destroy(richtek_spm_class);
|
||
|
return ret;
|
||
|
}
|
||
|
subsys_initcall(richtek_spm_init);
|
||
|
|
||
|
static void __exit richtek_spm_exit(void)
|
||
|
{
|
||
|
int i = 0;
|
||
|
|
||
|
for (i = 0; richtek_spm_class_attrs[i].attr.name; i++) {
|
||
|
class_remove_file(richtek_spm_class,
|
||
|
richtek_spm_class_attrs + i);
|
||
|
}
|
||
|
class_destroy(richtek_spm_class);
|
||
|
}
|
||
|
module_exit(richtek_spm_exit);
|
||
|
|
||
|
MODULE_LICENSE("GPL");
|
||
|
MODULE_DESCRIPTION("Richtek SPM Class driver");
|
||
|
MODULE_AUTHOR("CY Huang <cy_huang@richtek.com>");
|
||
|
MODULE_VERSION("1.0.6_S");
|