blob: 71a9f0c3fa02d4b137af701fb2e2613ec5078a1d [file] [log] [blame]
/*
* RGB-led driver for s2mu004
*
* Copyright (C) 2015 Samsung Electronics
*
* 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.
*/
#define pr_fmt(fmt) "[LED] " fmt
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/of_gpio.h>
#include <linux/slab.h>
#include <linux/interrupt.h>
#include <linux/init.h>
#include <linux/platform_device.h>
#include <linux/leds.h>
#include <linux/err.h>
#include <linux/of.h>
#include <linux/sec_sysfs.h>
#include <linux/mfd/samsung/s2mu004.h>
#include <linux/mfd/samsung/s2mu004-private.h>
#include <linux/leds-s2mu004-rgb.h>
struct s2mu004_rgb_drvdata {
struct s2mu004_rgb_pdata *pdata;
struct i2c_client *i2c;
struct device *dev;
u8 brightness;
u8 lpmode;
u32 on_delay;
u32 off_delay;
u32 ratio[S2MU004_LED_NUM];
};
static void s2mu004_rgb_set_state(struct s2mu004_rgb_drvdata *ddata,
enum led_color led, u32 brightness, unsigned int led_state)
{
int ret = 0;
int col = ddata->pdata->col[led];
u8 reg, val, mask;
u32 tmp;
if (brightness) {
tmp = (u32)(brightness * ddata->brightness);
tmp /= ddata->pdata->brightness;
if (tmp == 0)
tmp = 1;
reg = S2MU004_REG_LED1_CURRENT + col;
val = (u8)tmp;
pr_info("LED[%d] %u\n", led, brightness);
ret = s2mu004_write_reg(ddata->i2c, reg, val);
if (IS_ERR_VALUE(ret)) {
pr_err("failed to write brightness : %d\n", ret);
return;
}
val = led_state << ((S2MU004_LED_NUM - col - 1) << 1);
} else
val = LED_DISABLE;
reg = S2MU004_REG_LED_EN;
mask = RGBLED_ENMASK << ((S2MU004_LED_NUM - col - 1) << 1);
ret = s2mu004_update_reg(ddata->i2c, reg, val, mask);
if (IS_ERR_VALUE(ret))
pr_err("failed to LEDEN : %d\n", ret);
}
static int s2mu004_rgb_ramp(struct s2mu004_rgb_drvdata *ddata,
enum led_color led, int ramp_up, int ramp_down)
{
int ret = 0;
int value;
int col = ddata->pdata->col[led];
if (ramp_up > 800)
ramp_up = ((ramp_up - 800) >> 1) + 800;
ramp_up /= 100;
if (ramp_down > 800)
ramp_down = ((ramp_down - 800) >> 1) + 800;
ramp_down /= 100;
value = (ramp_down) | (ramp_up << 4);
ret = s2mu004_write_reg(ddata->i2c,
S2MU004_REG_LED1_RAMP + (col << 1), value);
if (IS_ERR_VALUE(ret)) {
pr_err("failed to write REG_LEDRMP : %d\n", ret);
return -ENODEV;
}
return 0;
}
static int s2mu004_rgb_blink(struct s2mu004_rgb_drvdata *ddata,
enum led_color led, unsigned int on, unsigned int off)
{
int ret = 0;
int value;
int col = ddata->pdata->col[led];
value = (LEDBLNK_ON(on) << 4) | LEDBLNK_OFF(off);
ret = s2mu004_write_reg(ddata->i2c,
S2MU004_REG_LED1_DUR + (col << 1), value);
if (IS_ERR_VALUE(ret)) {
pr_err("failed to write REG_LEDBLNK : %d\n", ret);
return -EINVAL;
}
pr_info("LED[%d] 0x%x\n", led, value);
return ret;
}
static void s2mu004_rgb_reset(struct s2mu004_rgb_drvdata *ddata)
{
int i = 0;
for (i = 0; i < ddata->pdata->nleds; i++) {
s2mu004_rgb_set_state(ddata, i, LED_OFF, LED_DISABLE);
s2mu004_rgb_ramp(ddata, i, 0, 0);
}
}
static ssize_t store_s2mu004_rgb_pattern(struct device *dev,
struct device_attribute *devattr, const char *buf, size_t count)
{
struct s2mu004_rgb_drvdata *ddata = dev_get_drvdata(dev);
int mode = 0;
int ret;
ret = sscanf(buf, "%1d", &mode);
if (ret == 0) {
pr_err("fail to get led_pattern\n");
return count;
}
pr_info("%s : %d lp : %u\n", __func__, mode, ddata->lpmode);
s2mu004_rgb_reset(ddata);
switch (mode) {
case CHARGING:
s2mu004_rgb_set_state(ddata, LED_RED,
ddata->ratio[LED_RED], LED_ALWAYS_ON);
break;
case CHARGING_ERR:
s2mu004_rgb_blink(ddata, LED_RED, 500, 500);
s2mu004_rgb_set_state(ddata, LED_RED,
ddata->ratio[LED_RED], LED_BLINK);
break;
case MISSED_NOTI:
s2mu004_rgb_blink(ddata, LED_BLUE, 500, 5000);
s2mu004_rgb_set_state(ddata, LED_BLUE,
ddata->ratio[LED_BLUE], LED_BLINK);
break;
case LOW_BATTERY:
s2mu004_rgb_blink(ddata, LED_RED, 500, 5000);
s2mu004_rgb_set_state(ddata, LED_RED,
ddata->ratio[LED_RED], LED_BLINK);
break;
case FULLY_CHARGED:
s2mu004_rgb_set_state(ddata, LED_GREEN,
ddata->ratio[LED_GREEN], LED_ALWAYS_ON);
break;
case POWERING:
s2mu004_rgb_ramp(ddata, LED_GREEN, 800, 800);
s2mu004_rgb_blink(ddata, LED_GREEN, 200, 200);
s2mu004_rgb_set_state(ddata, LED_BLUE,
ddata->ratio[LED_BLUE], LED_ALWAYS_ON);
s2mu004_rgb_set_state(ddata, LED_GREEN,
ddata->ratio[LED_GREEN], LED_BLINK);
break;
case PATTERN_OFF:
default:
break;
}
return count;
}
static ssize_t store_s2mu004_rgb_blink(struct device *dev,
struct device_attribute *devattr, const char *buf, size_t count)
{
struct s2mu004_rgb_drvdata *ddata = dev_get_drvdata(dev);
int val = 0;
int on = 0;
int off = 0;
int i = 0;
u8 br[S2MU004_LED_NUM];
if (sscanf(buf, "0x%8x %5d %5d", &val, &on, &off) != 3) {
pr_err("fail to get led_blink value.\n");
return count;
}
pr_info("%s on: %dms, off: %dms, col: 0x%x, lp: %u\n",
__func__, on, off, val, ddata->lpmode);
s2mu004_rgb_reset(ddata);
for (i = LED_BLUE; i >= LED_RED; i--) {
br[i] = val & 0xff;
val >>= 8;
if (br[i] > ddata->ratio[i])
br[i] = ddata->ratio[i];
}
for (i = 0; i < ddata->pdata->nleds; i++) {
if (br[i]) {
if (!off)
s2mu004_rgb_set_state(ddata, i, br[i], LED_ALWAYS_ON);
else {
s2mu004_rgb_set_state(ddata, i, br[i], LED_BLINK);
s2mu004_rgb_blink(ddata, i, on, off);
}
}
}
return count;
}
static ssize_t store_s2mu004_rgb_brightness(struct device *dev,
struct device_attribute *devattr, const char *buf, size_t count)
{
struct s2mu004_rgb_drvdata *ddata = dev_get_drvdata(dev);
int ret;
u8 brightness;
ret = kstrtou8(buf, 0, &brightness);
if (ret != 0) {
pr_err("fail to get led_brightness.\n");
return count;
}
ddata->lpmode = 0;
if (brightness > LED_MAX_CURRENT)
brightness = LED_MAX_CURRENT;
ddata->brightness = brightness;
pr_info("led brightness set to %u\n", brightness);
return count;
}
static ssize_t store_s2mu004_rgb_lowpower(struct device *dev,
struct device_attribute *devattr, const char *buf, size_t count)
{
struct s2mu004_rgb_drvdata *ddata = dev_get_drvdata(dev);
struct s2mu004_rgb_pdata *pdata = ddata->pdata;
u8 val;
int i = 0;
if (kstrtou8(buf, 0, &val)) {
pr_err("fail to get led_lowpower.\n");
return count;
}
ddata->lpmode = val;
pr_info("%s set to %u\n", __func__, val);
ddata->brightness = !val ? pdata->brightness : pdata->lp_brightness;
for (i = 0 ; i < ddata->pdata->nleds; i++)
ddata->ratio[i] = !val ? pdata->ratio[i] : pdata->ratio_low[i];
return count;
}
#define ATTR_STORE_RGB(name, type) \
static ssize_t store_led_##name(struct device *dev, \
struct device_attribute *devattr, const char *buf, size_t count)\
{ \
struct s2mu004_rgb_drvdata *ddata = dev_get_drvdata(dev); \
u8 val; \
\
if (kstrtou8(buf, 0, &val)) \
pr_err("fail to get brightness.\n"); \
else \
s2mu004_rgb_set_state(ddata, type, \
val, val ? LED_ALWAYS_ON : LED_DISABLE); \
pr_info("%s %u\n", __func__, val); \
return count; \
} \
static DEVICE_ATTR(led_##name, 0660, NULL, store_led_##name)
ATTR_STORE_RGB(r, LED_RED);
ATTR_STORE_RGB(g, LED_GREEN);
ATTR_STORE_RGB(b, LED_BLUE);
static DEVICE_ATTR(led_pattern, 0660, NULL, store_s2mu004_rgb_pattern);
static DEVICE_ATTR(led_blink, 0660, NULL, store_s2mu004_rgb_blink);
static DEVICE_ATTR(led_brightness, 0660, NULL, store_s2mu004_rgb_brightness);
static DEVICE_ATTR(led_lowpower, 0660, NULL, store_s2mu004_rgb_lowpower);
static struct attribute *sec_led_attributes[] = {
&dev_attr_led_r.attr,
&dev_attr_led_g.attr,
&dev_attr_led_b.attr,
&dev_attr_led_pattern.attr,
&dev_attr_led_blink.attr,
&dev_attr_led_brightness.attr,
&dev_attr_led_lowpower.attr,
NULL,
};
static struct attribute_group sec_led_attr_group = {
.attrs = sec_led_attributes,
};
static struct s2mu004_rgb_pdata *s2mu004_rgb_parse_dt(struct device *dev)
{
struct device_node *node, *pp;
struct s2mu004_rgb_pdata *pdata;
int i;
u32 col;
pdata = devm_kzalloc(dev, sizeof(*pdata), GFP_KERNEL);
if (unlikely(!pdata))
return ERR_PTR(-ENOMEM);
pdata->octa_color = (lcdtype >> 16) & 0x0000000f;
node = of_find_node_by_name(dev->parent->of_node, "s2mu004_led");
if (unlikely(!node)) {
pr_err("failed to find dt node\n");
return ERR_PTR(-EINVAL);
}
of_property_read_u32(node, "nleds", &pdata->nleds);
pdata->name = (const char *)of_get_property(node, "led_color", NULL);
for (i = 0; i < pdata->nleds; i++) {
switch (pdata->name[i]) {
case 'R':
pdata->col[i] = LED_RED;
break;
case 'G':
pdata->col[i] = LED_GREEN;
break;
case 'B':
pdata->col[i] = LED_BLUE;
break;
default:
break;
}
}
of_property_read_u32(node, "brightness", &pdata->brightness);
of_property_read_u32(node, "lp_brightness", &pdata->lp_brightness);
of_property_read_u32_array(node, "ratio",
pdata->ratio, S2MU004_LED_NUM);
of_property_read_u32_array(node, "ratio_low",
pdata->ratio_low, S2MU004_LED_NUM);
for_each_child_of_node(node, pp) {
of_property_read_u32(pp, "octa_color", &col);
if (pdata->octa_color != col)
continue;
of_property_read_u32_array(pp, "ratio",
pdata->ratio, S2MU004_LED_NUM);
of_property_read_u32_array(pp, "ratio_low",
pdata->ratio_low, S2MU004_LED_NUM);
}
pr_info("brightness : %d, lp_brightness : %d\n",
pdata->brightness, pdata->lp_brightness);
pr_info("octa_color 0x%x\n", pdata->octa_color);
for (i = 0; i < pdata->nleds; i++)
pr_info("%c ratio : %u ratio_low : %u\n",
pdata->name[i], pdata->ratio[i], pdata->ratio_low[i]);
return pdata;
}
static int s2mu004_rgb_probe(struct platform_device *pdev)
{
struct device *dev = &pdev->dev;
struct s2mu004_dev *s2mu004_dev = dev_get_drvdata(dev->parent);
struct s2mu004_rgb_pdata *pdata = dev_get_platdata(dev);
struct s2mu004_rgb_drvdata *ddata;
int ret = 0;
int i = 0;
if (!pdata) {
pdata = s2mu004_rgb_parse_dt(dev);
if (unlikely(!pdata))
return PTR_ERR(pdata);
}
ddata = devm_kzalloc(dev, sizeof(*ddata), GFP_KERNEL);
if (unlikely(!ddata)) {
pr_err("out of memory drv data\n");
return PTR_ERR(ddata);
}
platform_set_drvdata(pdev, ddata);
ddata->pdata = pdata;
ddata->brightness = pdata->brightness;
ddata->i2c = s2mu004_dev->i2c;
dev_set_drvdata(dev, ddata);
for (i = 0; i < pdata->nleds; i++)
ddata->ratio[i] = pdata->ratio[i];
ddata->dev = sec_device_create(ddata, "led");
if (IS_ERR(ddata->dev)) {
pr_err("Failed to create device for samsung specific led\n");
return PTR_ERR(ddata->dev);
}
ret = sysfs_create_group(&ddata->dev->kobj, &sec_led_attr_group);
if (ret < 0) {
pr_err("Failed to create sysfs group for samsung specific led\n");
goto err_sysfs_create;
}
return 0;
err_sysfs_create:
sec_device_destroy(ddata->dev->devt);
return ret;
}
static int s2mu004_rgb_remove(struct platform_device *pdev)
{
struct s2mu004_rgb_drvdata *ddata = platform_get_drvdata(pdev);
if (!ddata->i2c)
return 0;
s2mu004_rgb_reset(ddata);
sysfs_remove_group(&ddata->dev->kobj, &sec_led_attr_group);
return 0;
}
static void s2mu004_rgb_shutdown(struct platform_device *pdev)
{
struct s2mu004_rgb_drvdata *ddata = platform_get_drvdata(pdev);
if (!ddata->i2c)
return;
s2mu004_rgb_reset(ddata);
sysfs_remove_group(&ddata->dev->kobj, &sec_led_attr_group);
}
static struct platform_driver s2mu004_fled_driver = {
.driver = {
.name = "s2mu004-leds",
.owner = THIS_MODULE,
},
.probe = s2mu004_rgb_probe,
.remove = s2mu004_rgb_remove,
.shutdown = s2mu004_rgb_shutdown,
};
module_platform_driver(s2mu004_fled_driver);
MODULE_DESCRIPTION("s2mu004 LED driver");
MODULE_LICENSE("GPL v2");
MODULE_VERSION("1.0");