6db4831e98
Android 14
542 lines
14 KiB
C
542 lines
14 KiB
C
// SPDX-License-Identifier: GPL-2.0
|
|
/*
|
|
* Copyright (C) 2018 MediaTek Inc.
|
|
*
|
|
*/
|
|
|
|
#include <linux/init.h>
|
|
#include <linux/module.h>
|
|
#include <linux/device.h>
|
|
|
|
#include "mt_led_trigger.h"
|
|
|
|
const char *mt_led_trigger_mode_name[MT_LED_MODE_MAX] = {
|
|
"default_mode",
|
|
"register_mode",
|
|
"pwm_mode",
|
|
"breath_mode",
|
|
};
|
|
|
|
static ssize_t mt_led_register_attr_show(struct device *dev,
|
|
struct device_attribute *attr, char *buf);
|
|
static ssize_t mt_led_register_attr_store(struct device *dev,
|
|
struct device_attribute *attr, const char *buf, size_t cnt);
|
|
|
|
static const struct device_attribute mt_led_register_mode_attrs[] = {
|
|
__ATTR(soft_start_step, 0644,
|
|
mt_led_register_attr_show, mt_led_register_attr_store),
|
|
};
|
|
|
|
static ssize_t mt_led_register_attr_show(struct device *dev,
|
|
struct device_attribute *attr, char *buf)
|
|
{
|
|
struct led_classdev *led_cdev = dev_get_drvdata(dev);
|
|
struct mt_led_info *info = (struct mt_led_info *)led_cdev;
|
|
int ret = 0;
|
|
ptrdiff_t cmd;
|
|
unsigned int value;
|
|
|
|
cmd = attr - mt_led_register_mode_attrs;
|
|
buf[0] = '\0';
|
|
|
|
dev_info(led_cdev->dev, "%s cmd = %d\n", __func__, (int)cmd);
|
|
switch (cmd) {
|
|
case REGISTER_MODE_ATTR_SFSTR:
|
|
ret = info->ops->get_current_step(info, &value);
|
|
if (ret < 0)
|
|
return ret;
|
|
break;
|
|
default:
|
|
return -EINVAL;
|
|
}
|
|
|
|
snprintf(buf, PAGE_SIZE, "%d\n", value);
|
|
return strlen(buf);
|
|
}
|
|
|
|
static ssize_t mt_led_register_attr_store(struct device *dev,
|
|
struct device_attribute *attr, const char *buf, size_t cnt)
|
|
{
|
|
struct led_classdev *led_cdev = dev_get_drvdata(dev);
|
|
struct mt_led_info *info = (struct mt_led_info *)led_cdev;
|
|
int ret = 0;
|
|
unsigned long value = 0;
|
|
ptrdiff_t cmd;
|
|
|
|
cmd = attr - mt_led_register_mode_attrs;
|
|
|
|
ret = kstrtoul(buf, 10, &value);
|
|
if (ret < 0)
|
|
return ret;
|
|
|
|
dev_info(led_cdev->dev, "%s cmd = %d\n", __func__, (int)cmd);
|
|
switch (cmd) {
|
|
case REGISTER_MODE_ATTR_SFSTR:
|
|
ret = info->ops->set_current_step(info, value);
|
|
if (ret < 0)
|
|
return ret;
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
|
|
return cnt;
|
|
}
|
|
|
|
static int mt_led_register_activate(struct led_classdev *led)
|
|
{
|
|
struct mt_led_info *info = (struct mt_led_info *)led;
|
|
int i = 0, ret = 0;
|
|
|
|
if ((info->magic_code & MT_LED_MAGIC_MASK) == MT_LED_MAGIC_CODE &&
|
|
(info->magic_code & MT_LED_MAGIC_REGISTER_MODE) != 0) {
|
|
|
|
//check ops
|
|
if (info->ops == NULL) {
|
|
dev_notice(led->dev, "%s: operation is NULL\n", __func__);
|
|
return 0;
|
|
}
|
|
|
|
for (i = 0; i < ARRAY_SIZE(mt_led_register_mode_attrs); i++) {
|
|
ret = device_create_file(led->dev,
|
|
mt_led_register_mode_attrs + i);
|
|
if (ret < 0) {
|
|
dev_notice(led->dev,
|
|
"%s: create file fail %d\n",
|
|
__func__, i);
|
|
goto out_create_file;
|
|
}
|
|
}
|
|
|
|
ret = info->ops->change_mode(led, MT_LED_REGISTER_MODE);
|
|
if (ret < 0) {
|
|
dev_notice(led->dev, "%s: change mode fail\n", __func__);
|
|
goto out_change_mode;
|
|
}
|
|
|
|
return ret;
|
|
out_change_mode:
|
|
i = ARRAY_SIZE(mt_led_register_mode_attrs);
|
|
out_create_file:
|
|
while (--i > 0)
|
|
device_remove_file(led->dev, mt_led_register_mode_attrs + i);
|
|
} else
|
|
dev_notice(led->dev, "not Support MT REGISTER Trigger\n");
|
|
return ret;
|
|
}
|
|
|
|
static void mt_led_register_deactivate(struct led_classdev *led)
|
|
{
|
|
struct mt_led_info *info = (struct mt_led_info *)led;
|
|
int i = 0, ret = 0;
|
|
|
|
for (i = 0; i < ARRAY_SIZE(mt_led_register_mode_attrs); i++)
|
|
device_remove_file(led->dev, mt_led_register_mode_attrs + i);
|
|
|
|
ret = info->ops->change_mode(led, MT_LED_DEFAULT_MODE);
|
|
if (ret < 0)
|
|
dev_notice(led->dev, "%s change mode fail\n", __func__);
|
|
}
|
|
|
|
static ssize_t mt_led_pwm_attr_show(struct device *dev,
|
|
struct device_attribute *attr, char *buf);
|
|
static ssize_t mt_led_pwm_attr_store(struct device *dev,
|
|
struct device_attribute *attr, const char *buf, size_t cnt);
|
|
|
|
static const struct device_attribute mt_led_pwm_mode_attrs[] = {
|
|
__ATTR(pwm_dim_duty, 0644, mt_led_pwm_attr_show, mt_led_pwm_attr_store),
|
|
__ATTR(pwm_dim_freq, 0644, mt_led_pwm_attr_show, mt_led_pwm_attr_store),
|
|
__ATTR(list_duty, 0444, mt_led_pwm_attr_show, NULL),
|
|
__ATTR(list_freq, 0444, mt_led_pwm_attr_show, NULL),
|
|
};
|
|
|
|
static ssize_t mt_led_pwm_attr_show(struct device *dev,
|
|
struct device_attribute *attr, char *buf)
|
|
{
|
|
struct led_classdev *led_cdev = dev_get_drvdata(dev);
|
|
struct mt_led_info *info = (struct mt_led_info *)led_cdev;
|
|
int ret = 0;
|
|
ptrdiff_t cmd = attr - mt_led_pwm_mode_attrs;
|
|
//no float in kernel
|
|
int precision = 10000;
|
|
int value_HZ;
|
|
unsigned int dim_duty;
|
|
unsigned int dim_freq;
|
|
|
|
buf[0] = '\0';
|
|
|
|
dev_info(led_cdev->dev, "%s cmd = %d\n", __func__, (int)cmd);
|
|
switch (cmd) {
|
|
case PWM_MODE_ATTR_DIM_DUTY:
|
|
ret = info->ops->get_pwm_dim_duty(info, &dim_duty);
|
|
if (ret < 0)
|
|
return ret;
|
|
snprintf(buf, PAGE_SIZE, "%d (0~255)\n", dim_duty);
|
|
break;
|
|
case PWM_MODE_ATTR_DIM_FREQ:
|
|
ret = info->ops->get_pwm_dim_freq(info, &dim_freq);
|
|
if (ret < 0)
|
|
return ret;
|
|
value_HZ = (1000*precision)/((dim_freq+1)*2);
|
|
snprintf(buf, PAGE_SIZE, "%d.%d Hz\n", value_HZ/precision, value_HZ%precision);
|
|
break;
|
|
case PWM_MODE_ATTR_LIST_DUTY:
|
|
ret = info->ops->list_pwm_duty(info, buf);
|
|
break;
|
|
case PWM_MODE_ATTR_LIST_FREQ:
|
|
ret = info->ops->list_pwm_freq(info, buf);
|
|
break;
|
|
default:
|
|
return -EINVAL;
|
|
}
|
|
if (ret < 0)
|
|
return ret;
|
|
|
|
return strlen(buf);
|
|
}
|
|
|
|
static ssize_t mt_led_pwm_attr_store(struct device *dev,
|
|
struct device_attribute *attr, const char *buf, size_t cnt)
|
|
{
|
|
struct led_classdev *led_cdev = dev_get_drvdata(dev);
|
|
struct mt_led_info *info = (struct mt_led_info *)led_cdev;
|
|
int ret = 0;
|
|
unsigned long value = 0;
|
|
ptrdiff_t cmd = attr - mt_led_pwm_mode_attrs;
|
|
|
|
ret = kstrtoul(buf, 10, &value);
|
|
if (ret < 0)
|
|
return ret;
|
|
|
|
dev_info(led_cdev->dev, "%s cmd = %d\n", __func__, (int)cmd);
|
|
switch (cmd) {
|
|
case PWM_MODE_ATTR_DIM_DUTY:
|
|
ret = info->ops->set_pwm_dim_duty(info, value);
|
|
break;
|
|
case PWM_MODE_ATTR_DIM_FREQ:
|
|
ret = info->ops->set_pwm_dim_freq(info, value);
|
|
break;
|
|
default:
|
|
return -EINVAL;
|
|
}
|
|
if (ret < 0)
|
|
return ret;
|
|
|
|
return cnt;
|
|
}
|
|
|
|
static int mt_led_pwm_activate(struct led_classdev *led)
|
|
{
|
|
struct mt_led_info *info = (struct mt_led_info *)led;
|
|
int i = 0, ret = 0;
|
|
|
|
if ((info->magic_code & MT_LED_MAGIC_MASK) == MT_LED_MAGIC_CODE &&
|
|
(info->magic_code & MT_LED_MAGIC_PWM_MODE) != 0) {
|
|
|
|
//check ops
|
|
if (info->ops == NULL) {
|
|
dev_notice(led->dev, "%s: operation is NULL\n", __func__);
|
|
return 0;
|
|
}
|
|
|
|
for (i = 0; i < ARRAY_SIZE(mt_led_pwm_mode_attrs); i++) {
|
|
ret = device_create_file(led->dev,
|
|
mt_led_pwm_mode_attrs + i);
|
|
if (ret < 0) {
|
|
dev_notice(led->dev,
|
|
"%s: create file fail %d\n",
|
|
__func__, i);
|
|
goto out_create_file;
|
|
}
|
|
}
|
|
|
|
ret = info->ops->change_mode(led, MT_LED_PWM_MODE);
|
|
if (ret < 0) {
|
|
dev_notice(led->dev, "%s change pwm_mode fail\n", __func__);
|
|
goto out_change_mode;
|
|
}
|
|
|
|
return ret;
|
|
out_change_mode:
|
|
i = ARRAY_SIZE(mt_led_pwm_mode_attrs);
|
|
out_create_file:
|
|
while (--i > 0)
|
|
device_remove_file(led->dev, mt_led_pwm_mode_attrs + i);
|
|
} else
|
|
dev_notice(led->dev, "Not Support MT PWM Trigger\n");
|
|
return ret;
|
|
}
|
|
|
|
static void mt_led_pwm_deactivate(struct led_classdev *led)
|
|
{
|
|
struct mt_led_info *info = (struct mt_led_info *)led;
|
|
int i = 0, ret = 0;
|
|
|
|
for (i = 0; i < ARRAY_SIZE(mt_led_pwm_mode_attrs); i++)
|
|
device_remove_file(led->dev, mt_led_pwm_mode_attrs + i);
|
|
|
|
|
|
ret = info->ops->change_mode(led, MT_LED_DEFAULT_MODE);
|
|
if (ret < 0)
|
|
dev_notice(led->dev, "%s change mode fail\n", __func__);
|
|
}
|
|
|
|
static ssize_t mt_led_breath_attr_store(struct device *dev,
|
|
struct device_attribute *attr, const char *buf, size_t cnt);
|
|
static ssize_t mt_led_breath_attr_show(struct device *dev,
|
|
struct device_attribute *attr, char *buf);
|
|
|
|
static const struct device_attribute mt_led_breath_mode_attrs[] = {
|
|
__ATTR(tr1, 0644, mt_led_breath_attr_show, mt_led_breath_attr_store),
|
|
__ATTR(tr2, 0644, mt_led_breath_attr_show, mt_led_breath_attr_store),
|
|
__ATTR(tf1, 0644, mt_led_breath_attr_show, mt_led_breath_attr_store),
|
|
__ATTR(tf2, 0644, mt_led_breath_attr_show, mt_led_breath_attr_store),
|
|
__ATTR(ton, 0644, mt_led_breath_attr_show, mt_led_breath_attr_store),
|
|
__ATTR(toff, 0644, mt_led_breath_attr_show, mt_led_breath_attr_store),
|
|
__ATTR(list_time, 0444, mt_led_breath_attr_show, NULL),
|
|
};
|
|
|
|
static ssize_t mt_led_breath_attr_show(struct device *dev,
|
|
struct device_attribute *attr, char *buf)
|
|
{
|
|
int ret = 0;
|
|
ptrdiff_t cmd = attr - mt_led_breath_mode_attrs;
|
|
struct led_classdev *led_cdev = dev_get_drvdata(dev);
|
|
struct mt_led_info *info = (struct mt_led_info *)led_cdev;
|
|
unsigned int value;
|
|
|
|
buf[0] = '\0';
|
|
|
|
dev_info(led_cdev->dev, "%s cmd = %d\n", __func__, (int)cmd);
|
|
switch (cmd) {
|
|
case BREATH_MODE_ATTR_TR1:
|
|
ret = info->ops->get_breath_tr1(info, &value);
|
|
break;
|
|
case BREATH_MODE_ATTR_TR2:
|
|
ret = info->ops->get_breath_tr2(info, &value);
|
|
break;
|
|
case BREATH_MODE_ATTR_TF1:
|
|
ret = info->ops->get_breath_tf1(info, &value);
|
|
break;
|
|
case BREATH_MODE_ATTR_TF2:
|
|
ret = info->ops->get_breath_tf2(info, &value);
|
|
break;
|
|
case BREATH_MODE_ATTR_TON:
|
|
ret = info->ops->get_breath_ton(info, &value);
|
|
break;
|
|
case BREATH_MODE_ATTR_TOFF:
|
|
ret = info->ops->get_breath_toff(info, &value);
|
|
break;
|
|
default:
|
|
return -EINVAL;
|
|
}
|
|
if (ret < 0)
|
|
return ret;
|
|
|
|
snprintf(buf, PAGE_SIZE, "%d\n", value);
|
|
return strlen(buf);
|
|
}
|
|
|
|
static ssize_t mt_led_breath_attr_store(struct device *dev,
|
|
struct device_attribute *attr, const char *buf, size_t cnt)
|
|
{
|
|
struct led_classdev *led_cdev = dev_get_drvdata(dev);
|
|
struct mt_led_info *info = (struct mt_led_info *)led_cdev;
|
|
int ret = 0;
|
|
unsigned long value = 0;
|
|
ptrdiff_t cmd = attr - mt_led_breath_mode_attrs;
|
|
|
|
ret = kstrtoul(buf, 10, &value);
|
|
if (ret < 0)
|
|
return ret;
|
|
|
|
dev_info(led_cdev->dev, "%s cmd = %d\n", __func__, (int)cmd);
|
|
switch (cmd) {
|
|
case BREATH_MODE_ATTR_TR1:
|
|
ret = info->ops->set_breath_tr1(info, value);
|
|
break;
|
|
case BREATH_MODE_ATTR_TR2:
|
|
ret = info->ops->set_breath_tr2(info, value);
|
|
break;
|
|
case BREATH_MODE_ATTR_TF1:
|
|
ret = info->ops->set_breath_tf1(info, value);
|
|
break;
|
|
case BREATH_MODE_ATTR_TF2:
|
|
ret = info->ops->set_breath_tf2(info, value);
|
|
break;
|
|
case BREATH_MODE_ATTR_TON:
|
|
ret = info->ops->set_breath_ton(info, value);
|
|
break;
|
|
case BREATH_MODE_ATTR_TOFF:
|
|
ret = info->ops->set_breath_toff(info, value);
|
|
break;
|
|
default:
|
|
return -EINVAL;
|
|
}
|
|
|
|
if (ret < 0)
|
|
return ret;
|
|
|
|
return cnt;
|
|
}
|
|
|
|
static int mt_led_breath_activate(struct led_classdev *led)
|
|
{
|
|
struct mt_led_info *info = (struct mt_led_info *)led;
|
|
int i = 0, ret = 0;
|
|
|
|
if ((info->magic_code & MT_LED_MAGIC_MASK) == MT_LED_MAGIC_CODE &&
|
|
(info->magic_code & MT_LED_MAGIC_BREATH_MODE) != 0) {
|
|
|
|
//check ops
|
|
if (info->ops == NULL) {
|
|
dev_notice(led->dev, "%s: operation is NULL\n", __func__);
|
|
return 0;
|
|
}
|
|
|
|
for (i = 0; i < ARRAY_SIZE(mt_led_breath_mode_attrs); i++) {
|
|
ret = device_create_file(led->dev,
|
|
mt_led_breath_mode_attrs + i);
|
|
if (ret < 0) {
|
|
dev_notice(led->dev,
|
|
"%s: create file fail %d\n",
|
|
__func__, i);
|
|
goto out_create_file;
|
|
}
|
|
}
|
|
|
|
ret = info->ops->change_mode(led, MT_LED_BREATH_MODE);
|
|
if (ret < 0) {
|
|
dev_notice(led->dev, "%s change breath_mode fail: %d\n", __func__, ret);
|
|
goto out_change_mode;
|
|
}
|
|
|
|
return ret;
|
|
out_change_mode:
|
|
i = ARRAY_SIZE(mt_led_breath_mode_attrs);
|
|
out_create_file:
|
|
while (--i >= 0)
|
|
device_remove_file(
|
|
led->dev, mt_led_breath_mode_attrs + i);
|
|
} else
|
|
dev_notice(led->dev, "Not Support MT Breath Trigger\n");
|
|
return ret;
|
|
}
|
|
|
|
static void mt_led_breath_deactivate(struct led_classdev *led)
|
|
{
|
|
struct mt_led_info *info = (struct mt_led_info *)led;
|
|
int i = 0, ret = 0;
|
|
|
|
for (i = 0; i < ARRAY_SIZE(mt_led_breath_mode_attrs); i++)
|
|
device_remove_file(led->dev, mt_led_breath_mode_attrs + i);
|
|
|
|
ret = info->ops->change_mode(led, MT_LED_DEFAULT_MODE);
|
|
if (ret < 0)
|
|
dev_notice(led->dev, "%s change mode fail\n", __func__);
|
|
}
|
|
|
|
|
|
static struct led_trigger mt_led_trigger[] = {
|
|
{
|
|
.name = "register_mode",
|
|
.activate = mt_led_register_activate,
|
|
.deactivate = mt_led_register_deactivate,
|
|
},
|
|
{
|
|
.name = "pwm_mode",
|
|
.activate = mt_led_pwm_activate,
|
|
.deactivate = mt_led_pwm_deactivate,
|
|
},
|
|
{
|
|
.name = "breath_mode",
|
|
.activate = mt_led_breath_activate,
|
|
.deactivate = mt_led_breath_deactivate,
|
|
},
|
|
};
|
|
|
|
int mt_led_check_ops(struct mt_led_ops *ops)
|
|
{
|
|
if (!ops->get_current_step)
|
|
return -EINVAL;
|
|
if (!ops->set_current_step)
|
|
return -EINVAL;
|
|
if (!ops->get_pwm_dim_duty)
|
|
return -EINVAL;
|
|
if (!ops->set_pwm_dim_duty)
|
|
return -EINVAL;
|
|
if (!ops->get_pwm_dim_freq)
|
|
return -EINVAL;
|
|
if (!ops->set_pwm_dim_freq)
|
|
return -EINVAL;
|
|
if (!ops->get_breath_tr1)
|
|
return -EINVAL;
|
|
if (!ops->set_breath_tr1)
|
|
return -EINVAL;
|
|
if (!ops->get_breath_tr2)
|
|
return -EINVAL;
|
|
if (!ops->set_breath_tr2)
|
|
return -EINVAL;
|
|
if (!ops->get_breath_tf1)
|
|
return -EINVAL;
|
|
if (!ops->set_breath_tf1)
|
|
return -EINVAL;
|
|
if (!ops->get_breath_tf2)
|
|
return -EINVAL;
|
|
if (!ops->set_breath_tf2)
|
|
return -EINVAL;
|
|
if (!ops->get_breath_ton)
|
|
return -EINVAL;
|
|
if (!ops->set_breath_ton)
|
|
return -EINVAL;
|
|
if (!ops->get_breath_toff)
|
|
return -EINVAL;
|
|
if (!ops->set_breath_toff)
|
|
return -EINVAL;
|
|
if (!ops->list_pwm_duty)
|
|
return -EINVAL;
|
|
if (!ops->list_pwm_freq)
|
|
return -EINVAL;
|
|
return 0;
|
|
}
|
|
|
|
int mt_led_trigger_register(struct mt_led_ops *ops)
|
|
{
|
|
int i = 0, ret = 0;
|
|
|
|
ret = mt_led_check_ops(ops);
|
|
if (ret < 0) {
|
|
pr_notice("%s all ops need to be implment\n", __func__);
|
|
return -EINVAL;
|
|
}
|
|
|
|
for (i = 0; i < ARRAY_SIZE(mt_led_trigger); i++) {
|
|
ret = led_trigger_register(&mt_led_trigger[i]);
|
|
if (ret < 0) {
|
|
pr_notice("%s register led %d fail\n", __func__, i);
|
|
goto out_led_trigger;
|
|
}
|
|
}
|
|
|
|
return 0;
|
|
|
|
out_led_trigger:
|
|
while (--i > 0)
|
|
led_trigger_unregister(&mt_led_trigger[i]);
|
|
|
|
return -EINVAL;
|
|
}
|
|
|
|
void mt_led_trigger_unregister(void)
|
|
{
|
|
int i = 0;
|
|
|
|
for (i = 0; i < ARRAY_SIZE(mt_led_trigger); i++)
|
|
led_trigger_unregister(&mt_led_trigger[i]);
|
|
}
|
|
|
|
MODULE_AUTHOR("Mediatek Corporation>");
|
|
MODULE_DESCRIPTION("MT6357 PMIC LED Trigger");
|
|
MODULE_LICENSE("GPL");
|
|
MODULE_VERSION("1.0.0");
|