kernel_samsung_a34x-permissive/sound/soc/codecs/richtek_spm_cls.c
2024-04-28 15:51:13 +02:00

629 lines
16 KiB
C

// SPDX-License-Identifier: GPL-2.0
/*
* Copyright (c) 2021 Mediatek Inc.
*/
#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 <linux/i2c.h>
#include <sound/soc.h>
#include "../mediatek/common/mtk-sp-spk-amp.h"
#include "richtek_spm_cls.h"
enum {
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_RSPK,
RICHTEK_SPM_DEV_CALIBRATED,
RICHTEK_SPM_DEV_VVALIDATION_REAL_POWER,
RICHTEK_SPM_DEV_PCBTRACE,
RICHTEK_SPM_DEV_MAX,
};
#pragma pack(push, 1)
struct richtek_ipi_cmd {
int32_t data[16];
int32_t type;
int32_t cmd;
int32_t id;
} __aligned(32);
#pragma pack(pop)
enum {
RTK_IPI_TYPE_NORMAL = 0,
RTK_IPI_TYPE_CALIBRATION,
RTK_IPI_TYPE_NR,
};
enum {
RTK_IPI_CMD_CALIBRATION,
RTK_IPI_CMD_PCBTRACE,
RTK_IPI_CMD_VVALIDATION,
RTK_IPI_CMD_NR,
};
enum {
RICHTEK_SPM_CLASS_STATUS, /* this is for Samsung Calibration */
RICHTEK_SPM_CLASS_VVALIDATION,
RICHTEK_SPM_CLASS_MAX,
};
static struct class *richtek_spm_class;
static int cali_status;
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(status, 0664, richtek_spm_class_attr_show,
richtek_spm_class_attr_store),
__ATTR(v_status, 0664, richtek_spm_class_attr_show,
richtek_spm_class_attr_store),
__ATTR_NULL,
};
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;
pr_info("%s: %d\n", __func__, offset);
switch (offset) {
case RICHTEK_SPM_CLASS_STATUS:
case RICHTEK_SPM_CLASS_VVALIDATION:
ret = scnprintf(buf, PAGE_SIZE,
"%s\n", cali_status ? "Enabled" : "Disable");
break;
}
return ret;
}
static int rt_spm_trigger_calibration(struct device *dev, void *data)
{
struct richtek_spm_classdev *rdc = dev_get_drvdata(dev);
struct richtek_ipi_cmd ric;
int ret = 0;
u32 tmp = *(u32 *)data;
#if IS_ENABLED(CONFIG_SND_SOC_MTK_AUDIO_DSP)
uint32_t data_size = 0;
#endif
int polling_cnt = 6;
ric.type = RTK_IPI_TYPE_CALIBRATION;
ric.id = rdc->id;
if (tmp < 0 || tmp >= RTK_IPI_CMD_NR)
return -EINVAL;
rdc->calib_running = cali_status = 1;
switch (tmp) {
case 0:
ric.cmd = RTK_IPI_CMD_PCBTRACE;
if (rdc->ops && rdc->ops->pre_calib)
rdc->ops->pre_calib(rdc);
break;
case 1:
ric.cmd = RTK_IPI_CMD_CALIBRATION;
if (rdc->ops && rdc->ops->pre_calib)
rdc->ops->pre_calib(rdc);
break;
case 2:
ric.cmd = RTK_IPI_CMD_VVALIDATION;
if (rdc->ops && rdc->ops->pre_vvalid)
rdc->ops->pre_vvalid(rdc);
break;
default:
ric.cmd = tmp;
break;
}
#if IS_ENABLED(CONFIG_SND_SOC_MTK_AUDIO_DSP)
/* enable calibration mode */
ret = mtk_spk_send_ipi_buf_to_dsp(&ric, sizeof(ric));
#endif
if (ret < 0) {
pr_err("%s ret = %d\n", __func__, ret);
return -EINVAL;
}
do {
msleep(100);
/* get calibration result */
#if IS_ENABLED(CONFIG_SND_SOC_MTK_AUDIO_DSP)
ret = mtk_spk_recv_ipi_buf_from_dsp((int8_t *)&ric,
sizeof(ric),
&data_size);
#endif
if (ret == 0 && ric.data[0] == 0)
break;
} while (polling_cnt--);
if (!polling_cnt || ric.data[0]) {
switch (ric.cmd) {
case RTK_IPI_CMD_CALIBRATION:
dev_err(dev, "calibration failed...\n");
dev_err(dev, "%s status = %d, calib_dcr = %d\n",
__func__, ric.data[0], ric.data[1]);
if (ric.data[0] != 0)
rdc->rspk = 0;
if (rdc->ops && rdc->ops->post_calib)
rdc->ops->post_calib(rdc);
break;
case RTK_IPI_CMD_PCBTRACE:
dev_err(dev, "pcb trace failed...\n");
break;
case RTK_IPI_CMD_VVALIDATION:
dev_err(dev, "vvalidation failed...\n");
if (rdc->ops->post_vvalid)
rdc->ops->post_vvalid(rdc);
break;
default:
break;
}
} else {
switch (ric.cmd) {
case RTK_IPI_CMD_CALIBRATION:
dev_info(dev, "%s status = %d, calib_dcr = %d\n",
__func__, ric.data[0], ric.data[1]);
rdc->rspk = ric.data[1];
if (rdc->ops && rdc->ops->post_calib)
rdc->ops->post_calib(rdc);
break;
case RTK_IPI_CMD_PCBTRACE:
dev_info(dev, "%s status = %d, pcb trace = %d\n",
__func__, ric.data[0], ric.data[1]);
rdc->pcb_trace = ric.data[1];
if (rdc->ops && rdc->ops->post_calib)
rdc->ops->post_calib(rdc);
break;
case RTK_IPI_CMD_VVALIDATION:
dev_info(dev, "%s status = %d, pwr = %d, err = %d\n",
__func__, ric.data[0], ric.data[1],
ric.data[2]);
if (ric.data[2] == 0)
rdc->pwr = ric.data[1];
else
rdc->pwr = 0;
if (rdc->ops && rdc->ops->post_vvalid)
rdc->ops->post_vvalid(rdc);
break;
}
}
/* change to normal mode */
ric.type = RTK_IPI_TYPE_NORMAL;
#if IS_ENABLED(CONFIG_SND_SOC_MTK_AUDIO_DSP)
ret = mtk_spk_send_ipi_buf_to_dsp(&ric, sizeof(ric));
#endif
if (ret < 0) {
dev_err(dev, "%s ret = %d\n", __func__, ret);
rdc->calib_running = 0;
cali_status = 0;
return -EINVAL;
}
rdc->calib_running = 0;
cali_status = 0;
return 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;
pr_info("%s: %d\n", __func__, offset);
switch (offset) {
case RICHTEK_SPM_CLASS_STATUS:
ret = kstrtou32(buf, 0, &tmp);
if (ret < 0)
return ret;
ret = class_for_each_device(cls, NULL, &tmp,
rt_spm_trigger_calibration);
if (ret < 0)
return ret;
break;
case RICHTEK_SPM_CLASS_VVALIDATION:
ret = kstrtou32(buf, 0, &tmp);
if (ret < 0)
return ret;
if (tmp == 1)
tmp = 2;
else
break;
ret = class_for_each_device(cls, NULL, &tmp,
rt_spm_trigger_calibration);
if (ret < 0)
return ret;
break;
default:
return -EINVAL;
}
return cnt;
}
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(tmax, 0444,
richtek_spm_dev_attr_show, NULL),
__ATTR(tmaxcnt, 0444,
richtek_spm_dev_attr_show, NULL),
__ATTR(xmax, 0444,
richtek_spm_dev_attr_show, NULL),
__ATTR(xmaxcnt, 0444,
richtek_spm_dev_attr_show, NULL),
__ATTR(tmaxkeep, 0444,
richtek_spm_dev_attr_show, NULL),
__ATTR(xmaxkeep, 0444,
richtek_spm_dev_attr_show, NULL),
__ATTR(rspk, 0644,
richtek_spm_dev_attr_show, richtek_spm_dev_attr_store),
__ATTR(calibrated, 0644,
richtek_spm_dev_attr_show, richtek_spm_dev_attr_store),
__ATTR(pwr, 0444,
richtek_spm_dev_attr_show, NULL),
__ATTR(pcb_trace, 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,
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,
};
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_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;
u32 tmp = 0;
int ret = 0;
switch (offset) {
case RICHTEK_SPM_DEV_RSPK:
ret = kstrtou32(buf, 0, &tmp);
if (ret < 0)
return ret;
rdc->rspk = tmp;
dev_info(dev, "%s: rspk = %d\n", __func__, rdc->rspk);
break;
case RICHTEK_SPM_DEV_CALIBRATED:
ret = kstrtou32(buf, 0, &tmp);
if (ret < 0)
return ret;
if (tmp == 1)
rdc->calibrated = 1;
break;
default:
break;
};
return cnt;
}
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;
int ret = -EINVAL;
dev_info(dev, "%s: %d\n", __func__, offset);
mutex_lock(&rdc->var_lock);
switch (offset) {
case RICHTEK_SPM_DEV_RSPK:
ret = scnprintf(buf, PAGE_SIZE, "%d\n", rdc->rspk);
break;
case RICHTEK_SPM_DEV_CALIBRATED:
ret = scnprintf(buf, PAGE_SIZE, "%d\n", rdc->calibrated);
break;
case RICHTEK_SPM_DEV_PCBTRACE:
ret = scnprintf(buf, PAGE_SIZE, "%d\n", rdc->pcb_trace);
break;
case RICHTEK_SPM_DEV_VVALIDATION_REAL_POWER:
ret = scnprintf(buf, PAGE_SIZE, "%d\n",
(rdc->pwr >= rdc->min_pwr && rdc->pwr <= rdc->max_pwr) ? 1 : 0);
dev_info(dev, "%s pwr = %d.%dW\n", __func__, rdc->pwr/1000,
rdc->pwr%1000);
break;
case RICHTEK_SPM_DEV_TMAX:
ret = rt_spm_monitor_convert(offset, buf, PAGE_SIZE,
rdc->tmax);
break;
case RICHTEK_SPM_DEV_TMAXCNT:
ret = rt_spm_monitor_convert(offset, buf, PAGE_SIZE,
rdc->tmaxcnt);
break;
case RICHTEK_SPM_DEV_XMAX:
ret = rt_spm_monitor_convert(offset, buf, PAGE_SIZE,
rdc->xmax);
break;
case RICHTEK_SPM_DEV_XMAXCNT:
ret = rt_spm_monitor_convert(offset, buf, PAGE_SIZE,
rdc->xmaxcnt);
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;
};
mutex_unlock(&rdc->var_lock);
return ret;
}
#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;
if (rdc == NULL)
return -EINVAL;
if (parent == NULL && rdc->name == NULL) {
pr_err("[richtek_spm] no name can be specified\n");
return -EINVAL;
}
if (rdc->name == NULL) {
rdc->name = devm_kasprintf(parent,
GFP_KERNEL, "rt_amp%d", spk_index);
if (!rdc->name)
return -ENOMEM;
}
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 */
mutex_init(&rdc->var_lock);
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)
{
return 0;
}
static int rt_spm_classdev_ampoff(struct richtek_spm_classdev *rdc)
{
int ret;
#if IS_ENABLED(CONFIG_SND_SOC_MTK_AUDIO_DSP)
uint32_t data_size = 0;
#endif
struct richtek_ipi_cmd ric;
if (!rdc)
return -EINVAL;
#if IS_ENABLED(CONFIG_SND_SOC_MTK_AUDIO_DSP)
ret = mtk_spk_recv_ipi_buf_from_dsp((int8_t *)&ric, sizeof(ric),
&data_size);
#endif
if (ret < 0) {
pr_err("%s recv big data ipi failed\n", __func__);
return ret;
}
rdc->tmax = ric.data[0];
rdc->tmaxcnt = ric.data[1];
rdc->xmax = ric.data[2];
rdc->xmaxcnt = ric.data[3];
pr_info("%s richtek tmax = %d, tmaxcnt = %d, xmax = %d, xmaxcnt = %d\n",
__func__, rdc->tmax, rdc->tmaxcnt, rdc->xmax, rdc->xmaxcnt);
mutex_lock(&rdc->var_lock);
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 BIGDATA Class driver");
MODULE_AUTHOR("Jeff Chang <jeff_chang@richtek.com>");
MODULE_VERSION("1.0.4_G");
/* 1.0.1_G
* 1. implement calibration interface like LSI Platform
* 1.0.2_G
* 1. modify calibration trigger node path
* 1.0.3_G
* 1. modify calibration command and add v_status for vvalidation
* 1.0.4_G
* 1. change pwr node show information
* 2. update check patch
*/