blob: a8e52995d9ca2d15fd589967e6dea84eb1651b21 [file] [log] [blame]
/*
* 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");