sdk-hwV1.3/lichee/linux-4.9/drivers/leds/leds-is31fl3736.c

1066 lines
26 KiB
C
Raw Normal View History

2024-05-07 10:09:20 +00:00
/*
* drivers/leds/is31fl3736.c
* Driver for ISSI is31fl3736 of I2C LED controllers
*
* Copyright (C) 2018 Allwinner Technology Limited. All rights reserved.
*
*
* 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/device.h>
#include <linux/i2c.h>
#include <linux/kernel.h>
#include <linux/leds.h>
#include <linux/module.h>
#include <linux/delay.h>
#include <linux/of.h>
#include <linux/of_device.h>
#include <linux/gpio.h>
#include <linux/sunxi-gpio.h>
#include <linux/of_gpio.h>
#include <linux/io.h>
#include "leds-is31fl3736.h"
#define IS31FL3736_WRITE_BIT 0x00
#define IS31FL3736_READ_BIT 0x01
#define IS31FL3736_ACK_CHECK_EN 1
#define IS31FL3736_READ_ACK 0x0 /*!< I2C ack value */
#define IS31FL3736_READ_NACK 0x1 /*!< I2C nack value */
#define IS31FL3736_CMD_WRITE_EN 0xC5 /*!< magic num */
#define IS31FL3736_I2C_ID 0xA0 /*!< I2C Addr,up to ADDR1/ADDR2 pin*/
/* Used to indicate a device has no such register */
#define IS31FL37XX_REG_NONE 0xFF
/*!< max channel num in the PWM mode */
#define IS31FL3736_PWM_CHANNEL_MAX (0XBE)
/*!< in LED matrix mode, there are 8 current sources channel */
#define IS31FL3736_CSX_MAX (8)
#define IS31FL3736_CSX_MAX_MASK (0xff)
/*!< in LED matrix mode, there are 12 switch channel */
#define IS31FL3736_SWY_MAX (12)
#define IS31FL3736_SWY_MAX_MASK (0xfff)
#define IS31FL3736_PAGE(i) (i) /*!< range 0 ~ 3 */
enum is31fl3736_t1_t3_time {
IS31FL3736_t1_t3_time_0 = 0,/*!< t1/t3, 0.21s */
IS31FL3736_t1_t3_time_1 = 0x01, /*!< t1/t3, 0.42s */
IS31FL3736_t1_t3_time_2 = 0x02, /*!< t1/t3, 0.84s */
IS31FL3736_t1_t3_time_3 = 0x03, /*!< t1/t3, 1.68s*/
IS31FL3736_t1_t3_time_4 = 0x04, /*!< t1/t3, 3.36s */
IS31FL3736_t1_t3_time_5 = 0x05, /*!< t1/t3, 6.72s */
IS31FL3736_t1_t3_time_6 = 0x06, /*!< t1/t3, 13.44s */
IS31FL3736_t1_t3_time_7 = 0x07, /*!< t1/t3, 26.88s */
IS31FL3736_t1_t3_time_MAX,
};
enum is31fl3736_t2_time {
IS31FL3736_t2_time_0 = 0,/*!< t2, 0s */
IS31FL3736_t2_time_1 = 0x01, /*!< t2, 0.21s */
IS31FL3736_t2_time_2 = 0x02, /*!< t2, 0.42s */
IS31FL3736_t2_time_3 = 0x03, /*!< t2, 0.84s */
IS31FL3736_t2_time_4 = 0x04, /*!< t2, 1.68s */
IS31FL3736_t2_time_5 = 0x05, /*!< t2, 3.36s */
IS31FL3736_t2_time_6 = 0x06, /*!< t2, 6.72s */
IS31FL3736_t2_time_7 = 0x07, /*!< t2, 13.44s */
IS31FL3736_t2_time_8 = 0x08, /*!< t2, 26.88s */
IS31FL3736_t2_time_MAX,
};
enum is31fl3736_t4_time {
IS31FL3736_t4_time_0 = 0,/*!< t4, 0s */
IS31FL3736_t4_time_1 = 0x01, /*!< t4, 0.21s */
IS31FL3736_t4_time_2 = 0x02, /*!< t4, 0.42s */
IS31FL3736_t4_time_3 = 0x03, /*!< t4, 0.84s */
IS31FL3736_t4_time_4 = 0x04, /*!< t4, 1.68s */
IS31FL3736_t4_time_5 = 0x05, /*!< t4, 3.36s */
IS31FL3736_t4_time_6 = 0x06, /*!< t4, 6.72s */
IS31FL3736_t4_time_7 = 0x07, /*!< t4, 13.44s */
IS31FL3736_t4_time_8 = 0x08, /*!< t4, 26.88s */
IS31FL3736_t4_time_9 = 0x09, /*!< t4, 53.76s */
IS31FL3736_t4_time_10 = 0x10, /*!< t4, 107.52s */
IS31FL3736_t4_time_MAX,
};
enum is31fl3736_led_state {
IS31FL3736_LED_OFF = 0, /*!< The resistor value */
IS31FL3736_LED_ON = 1, /*!< The resistor value */
IS31FL3736_LED_MAX,
};
enum is31fl3736_auto_breath_mode {
IS31FL3736_PWM_MODE = 0, /*!< The resistor value */
IS31FL3736_ABM1 = 1, /*!< The resistor value */
IS31FL3736_ABM2 = 2, /*!< The resistor value */
IS31FL3736_ABM3 = 3, /*!< The resistor value */
IS31FL3736_ABM_MAX,
};
struct is31fl3736_led_data {
struct led_classdev cdev;
u8 channel; /* 1-based, max */
u8 sw_ch;
u8 cs_ch;
/*Auto breath control time set*/
u8 rise_time; /*t1*/
u8 hold_time; /*t2*/
u8 fall_time; /*t3*/
u8 off_time; /*t4*/
u8 cur_duty; /*pwm duty*/
u32 loop;/*blink time, if 0, endless blink*/
enum is31fl3736_led_state cur_state;
enum is31fl3736_auto_breath_mode cur_mode;
struct is31fl3736_priv *priv;
};
struct is31fl3736_priv {
u32 power_gpio;
u32 shdn_gpio;
u32 int_gpio;
u16 sw_y[IS31FL3736_SWY_MAX];
u16 num_leds;
u8 cs_max;
u8 sw_max;
struct i2c_client *client;
struct mutex mutex;
struct is31fl3736_led_data leds[0];
};
#define IS31Fl3736_ATTR_RW(_name) \
DEVICE_ATTR(_name, 0644, is31fl3736_led_show_##_name, \
is31fl3736_led_store_##_name)
static int is31fl3736_read_byte(struct is31fl3736_priv *priv,
u8 reg, u8 *rt_value)
{
int ret;
u8 read_cmd[3] = { 0 };
u8 cmd_len = 0;
read_cmd[0] = reg;
cmd_len = 1;
if (priv->client->adapter == NULL)
pr_err("is31fl3736_read client->adapter==NULL\n");
ret = i2c_master_send(priv->client, read_cmd, cmd_len);
if (ret != cmd_len) {
pr_err("is31fl3736_read error1\n");
return -1;
}
ret = i2c_master_recv(priv->client, rt_value, 1);
if (ret != 1) {
pr_err("is31fl3736_read error2, ret = %d.\n", ret);
return -1;
}
return 0;
}
static int is31fl3736_write_byte(struct is31fl3736_priv *priv,
u8 reg, u8 value)
{
int ret = 0;
u8 write_cmd[2] = { 0 };
write_cmd[0] = reg;
write_cmd[1] = value;
ret = i2c_master_send(priv->client, write_cmd, 2);
if (ret != 2) {
pr_err("is31fl3736_write error->[REG-0x%02x,val-0x%02x]\n",
reg, value);
return -1;
}
return 0;
}
/**
* @brief change led channels on/off state
*/
static int is31fl3736_set_led_state(struct is31fl3736_priv *priv,
u8 cs_x, u8 sw_y, enum is31fl3736_led_state state)
{
u8 reg, reg_val;
int ret = 0;
pr_debug("%s: cs: %d, sw: %d, state: %d\n",
__func__, cs_x, sw_y, state);
if (cs_x > IS31FL3736_CSX_MAX || cs_x < 1) {
pr_err("%s: cs channel error, cs_x: %d\n", __func__, cs_x);
return -EINVAL;
}
if (sw_y > IS31FL3736_SWY_MAX || sw_y < 1) {
pr_err("%s: sw channel error, sw_y: %d\n", __func__, sw_y);
return -EINVAL;
}
ret = is31fl3736_write_byte(priv,
IS31FL3736_RET_CMD_LOCK, IS31FL3736_CMD_WRITE_EN);
if (ret)
return ret;
ret = is31fl3736_write_byte(priv,
IS31FL3736_REG_CMD, IS31FL3736_PAGE(0));
if (ret)
return ret;
reg = (sw_y - 1) * 2 + ((cs_x - 1) / 4);
if (state == IS31FL3736_LED_ON)
*(priv->sw_y + sw_y - 1) |= state << (cs_x - 1) * 2;
else
*(priv->sw_y + sw_y - 1) &= state << (cs_x - 1) * 2;
if (cs_x > 4)
reg_val = (*(priv->sw_y + sw_y - 1) >> 8) & 0xff;//high 8 bit
else
reg_val = *(priv->sw_y + sw_y - 1) & 0xff;//low 8 bit
pr_debug("%s: reg 0x%02x, val 0x%02x\n", __func__, reg, reg_val);
return is31fl3736_write_byte(priv, reg, reg_val);
}
/**
* @brief change channels pwm duty register
*/
static int is31fl3736_set_pwm_duty(struct is31fl3736_priv *priv,
u8 cs_x, u8 sw_y, u8 duty)
{
u8 reg;
int ret = 0;
if (cs_x > IS31FL3736_CSX_MAX || cs_x < 1) {
pr_err("%s: cs channel error, cs_x: %d\n", __func__, cs_x);
return -EINVAL;
}
if (sw_y > IS31FL3736_SWY_MAX || sw_y < 1) {
pr_err("%s: sw channel error, sw_y: %d\n", __func__, sw_y);
return -EINVAL;
}
ret = is31fl3736_write_byte(priv,
IS31FL3736_RET_CMD_LOCK, IS31FL3736_CMD_WRITE_EN);
if (ret)
return ret;
ret = is31fl3736_write_byte(priv,
IS31FL3736_REG_CMD, IS31FL3736_PAGE(1));
if (ret)
return ret;
reg = 0x10 * (sw_y - 1) + 0x02 * (cs_x - 1);
pr_debug("%s: reg 0x%02x, duty %d\n", __func__, reg, duty);
return is31fl3736_write_byte(priv, reg, duty); //pwm-duty
}
/**
* @brief change the breath mode for each led bit
*/
static int is31fl3736_set_breath_mode(struct is31fl3736_priv *priv,
u8 cs_x, u8 sw_y, enum is31fl3736_auto_breath_mode mode)
{
u8 reg;
int ret = 0;
if (cs_x > IS31FL3736_CSX_MAX || cs_x < 1) {
pr_err("%s: cs channel error, cs_x: %d\n", __func__, cs_x);
return -EINVAL;
}
if (sw_y > IS31FL3736_SWY_MAX || sw_y < 1) {
pr_err("%s: sw channel error, sw_y: %d\n", __func__, sw_y);
return -EINVAL;
}
ret = is31fl3736_write_byte(priv,
IS31FL3736_RET_CMD_LOCK, IS31FL3736_CMD_WRITE_EN);
if (ret)
return ret;
ret = is31fl3736_write_byte(priv,
IS31FL3736_REG_CMD, IS31FL3736_PAGE(2));
if (ret)
return ret;
reg = 0x10 * (sw_y - 1) + 0x02 * (cs_x - 1);
pr_debug("%s: reg 0x%02x, mode %d\n", __func__, reg, mode);
return is31fl3736_write_byte(priv, reg, mode); //ABM-x
}
/**
* @brief set led time delay for fade and holdtime
*/
static int is31fl3736_set_breath_time(struct is31fl3736_priv *priv,
u8 rise, u8 hold, u8 fall, u8 off,
enum is31fl3736_auto_breath_mode mode)
{
int ret;
u8 regval;
pr_debug("%s: set led breah mode:%d time: t1: %d, t2: %d, t3: %d, t3: %d\n",
__func__, mode, rise, hold, fall, off);
if (mode == IS31FL3736_PWM_MODE)
return -EINVAL;
ret = is31fl3736_write_byte(priv,
IS31FL3736_RET_CMD_LOCK, IS31FL3736_CMD_WRITE_EN);
if (ret)
return ret;
ret = is31fl3736_write_byte(priv,
IS31FL3736_REG_CMD, IS31FL3736_PAGE(3));
if (ret)
return ret;
regval = ((rise & IS31FL3736_ABM_T1_V) << IS31FL3736_ABM_T1_S) |
((hold & IS31FL3736_ABM_T2_V) << IS31FL3736_ABM_T2_S);
pr_debug("%s: reg: 0x%0x, val: %d\n",
__func__, IS31FL3736_REG_PG3_FADE_IN(mode), regval);
ret = is31fl3736_write_byte(priv,
IS31FL3736_REG_PG3_FADE_IN(mode), regval);
if (ret)
return ret;
regval = ((fall & IS31FL3736_ABM_T3_V) << IS31FL3736_ABM_T3_S) |
((off & IS31FL3736_ABM_T4_V) << IS31FL3736_ABM_T4_S);
pr_debug("%s: reg: 0x%0x, val: %d\n",
__func__, IS31FL3736_REG_PG3_FADE_OUT(mode), regval);
ret = is31fl3736_write_byte(priv,
IS31FL3736_REG_PG3_FADE_OUT(mode), regval);
return ret;
}
/**
* @brief set led loop time for brath mode
*/
static int is31fl3736_set_breath_loop(struct is31fl3736_priv *priv,
u32 loop, enum is31fl3736_auto_breath_mode mode)
{
int ret;
pr_debug("%s: set led blink loop times, mode: %d, loop: %d\n",
__func__, mode, loop);
if (mode == IS31FL3736_PWM_MODE)
return -EINVAL;
ret = is31fl3736_write_byte(priv,
IS31FL3736_RET_CMD_LOCK, IS31FL3736_CMD_WRITE_EN);
if (ret)
return ret;
ret = is31fl3736_write_byte(priv,
IS31FL3736_REG_CMD, IS31FL3736_PAGE(3));
if (ret)
return ret;
/*LTA, high 4 bit*/
ret = is31fl3736_write_byte(priv, IS31FL3736_REG_PG3_LOOP1(mode),
(loop >> 8) & IS31FL3736_ABM_LTA_V);
if (ret)
return ret;
/*LTB, low 8 bits, total loop time: LTA*256 + LTB*/
ret = is31fl3736_write_byte(priv, IS31FL3736_REG_PG3_LOOP2(mode),
loop & IS31FL3736_ABM_LTB_V);
return ret;
}
/**
* @brief set led blink for brath mode, begin or stop blink,
just influent the ARMx mode led
*/
static int is31fl3736_set_breath_blink(struct is31fl3736_priv *priv, int enable)
{
int ret;
pr_debug("%s: set led blink enable: %d\n", __func__, enable);
ret = is31fl3736_write_byte(priv, IS31FL3736_RET_CMD_LOCK,
IS31FL3736_CMD_WRITE_EN);
if (ret)
return ret;
ret = is31fl3736_write_byte(priv,
IS31FL3736_REG_CMD, IS31FL3736_PAGE(3));
if (ret)
return ret;
if (enable) {
is31fl3736_write_byte(priv, IS31FL3736_REG_PG3_CONFIG, 0x03);
//update registers
ret = is31fl3736_write_byte(priv,
IS31FL3736_REG_PG3_UPDATE, 0x00);
} else {
//disable auto breath mode
ret = is31fl3736_write_byte(priv,
IS31FL3736_REG_PG3_CONFIG, 0x01);
}
return ret;
}
/**
* @brief reset all regs to default
*/
static int is31fl3736_reset_regs(struct is31fl3736_priv *priv)
{
int ret;
u8 reg;
ret = is31fl3736_write_byte(priv,
IS31FL3736_RET_CMD_LOCK, IS31FL3736_CMD_WRITE_EN);
if (ret)
return ret;
ret = is31fl3736_write_byte(priv,
IS31FL3736_REG_CMD, IS31FL3736_PAGE(3));
if (ret)
return ret;
ret = is31fl3736_read_byte(priv,
IS31FL3736_REG_PG3_RESET, &reg);
if (ret)
return ret;
return 0;
}
/**
* @brief software shutdown the chip
*/
static int is31fl3736_software_shutdown(struct is31fl3736_priv *priv,
bool enable)
{
int ret;
ret = is31fl3736_write_byte(priv,
IS31FL3736_RET_CMD_LOCK, IS31FL3736_CMD_WRITE_EN);
if (ret)
return ret;
ret = is31fl3736_write_byte(priv,
IS31FL3736_REG_CMD, IS31FL3736_PAGE(3));
if (ret)
return ret;
if (enable) {
//software shutdown
ret = is31fl3736_write_byte(priv,
IS31FL3736_REG_PG3_CONFIG, 0x00);
} else {
//normal state
ret = is31fl3736_write_byte(priv,
IS31FL3736_REG_PG3_CONFIG, 0x03);
}
return ret;
}
/**
* @brief init the chip to normal state
*/
static int is31fl3736_init_regs(struct is31fl3736_priv *priv)
{
is31fl3736_write_byte(priv,
IS31FL3736_RET_CMD_LOCK, IS31FL3736_CMD_WRITE_EN);
is31fl3736_write_byte(priv, IS31FL3736_REG_PG3_CONFIG, 0x03);
is31fl3736_write_byte(priv, IS31FL3736_REG_PG3_CURR, 0x0ff);
return 0;
}
/**
* @brief set the led chanel brightness
*/
static int is31fl3736_brightness_set(struct led_classdev *led_cdev,
enum led_brightness brightness)
{
struct is31fl3736_led_data *led_data =
container_of(led_cdev, struct is31fl3736_led_data, cdev);
int ret;
enum is31fl3736_led_state state;
dev_dbg(led_cdev->dev, "%s: %d, cs: %d, sw: %d\n",
__func__, brightness,
led_data->cs_ch, led_data->sw_ch);
if (led_data->cur_mode != IS31FL3736_PWM_MODE) {
dev_info(led_cdev->dev,
"%s: current mode is not PWM mode\n", __func__);
return -EPERM;
}
if (brightness < 0 || brightness > 0xff) {
dev_err(led_cdev->dev,
"%s: brightness: %d is invalid\n",
__func__, brightness);
return -EINVAL;
}
state = brightness ? IS31FL3736_LED_ON : IS31FL3736_LED_OFF;
is31fl3736_set_led_state(led_data->priv,
led_data->cs_ch, led_data->sw_ch, IS31FL3736_LED_ON);
led_data->cur_state = state;
ret = is31fl3736_set_pwm_duty(led_data->priv,
led_data->cs_ch, led_data->sw_ch,
brightness);
return ret;
}
static ssize_t is31fl3736_led_show_mode(struct device *dev,
struct device_attribute *attr, char *buf)
{
struct led_classdev *led_cdev = dev_get_drvdata(dev);
struct is31fl3736_led_data *led_data =
container_of(led_cdev, struct is31fl3736_led_data, cdev);
ssize_t ret = 0;
mutex_lock(&led_data->priv->mutex);
switch (led_data->cur_mode) {
case IS31FL3736_PWM_MODE:
ret += sprintf(buf, "PWM\n");
break;
case IS31FL3736_ABM1:
ret += sprintf(buf, "ABM1\n");
break;
case IS31FL3736_ABM2:
ret += sprintf(buf, "ABM2\n");
break;
case IS31FL3736_ABM3:
ret += sprintf(buf, "ABM3\n");
break;
default:
ret += sprintf(buf, "NONE\n");
break;
}
mutex_unlock(&led_data->priv->mutex);
return ret;
}
static ssize_t is31fl3736_led_store_mode(struct device *dev,
struct device_attribute *attr,
const char *buf, size_t size)
{
struct led_classdev *led_cdev = dev_get_drvdata(dev);
struct is31fl3736_led_data *led_data =
container_of(led_cdev, struct is31fl3736_led_data, cdev);
enum is31fl3736_auto_breath_mode mode;
int ret;
mutex_lock(&led_data->priv->mutex);
if (!strncmp(buf, "PWM", 3))
mode = IS31FL3736_PWM_MODE;
else if (!strncmp(buf, "ABM1", 4))
mode = IS31FL3736_ABM1;
else if (!strncmp(buf, "ABM2", 4))
mode = IS31FL3736_ABM2;
else if (!strncmp(buf, "ABM3", 4))
mode = IS31FL3736_ABM3;
else
mode = IS31FL3736_PWM_MODE;
ret = is31fl3736_set_breath_mode(led_data->priv,
led_data->cs_ch,
led_data->sw_ch, mode);
if (!ret)
led_data->cur_mode = mode;
mutex_unlock(&led_data->priv->mutex);
return size;
}
static ssize_t is31fl3736_led_show_state(struct device *dev,
struct device_attribute *attr, char *buf)
{
struct led_classdev *led_cdev = dev_get_drvdata(dev);
struct is31fl3736_led_data *led_data =
container_of(led_cdev, struct is31fl3736_led_data, cdev);
ssize_t ret = 0;
mutex_lock(&led_data->priv->mutex);
switch (led_data->cur_state) {
case IS31FL3736_LED_OFF:
ret += sprintf(buf, "OFF\n");
break;
case IS31FL3736_LED_ON:
ret += sprintf(buf, "ON\n");
break;
default:
ret += sprintf(buf, "NONE\n");
break;
}
mutex_unlock(&led_data->priv->mutex);
return ret;
}
static ssize_t is31fl3736_led_store_state(struct device *dev,
struct device_attribute *attr,
const char *buf, size_t size)
{
struct led_classdev *led_cdev = dev_get_drvdata(dev);
struct is31fl3736_led_data *led_data =
container_of(led_cdev, struct is31fl3736_led_data, cdev);
enum is31fl3736_led_state state;
int ret;
mutex_lock(&led_data->priv->mutex);
if (!strncmp(buf, "OFF", 3) || !strncmp(buf, "off", 3))
state = IS31FL3736_LED_OFF;
else if (!strncmp(buf, "ON", 2) || !strncmp(buf, "on", 2))
state = IS31FL3736_LED_ON;
else
state = IS31FL3736_LED_OFF;
ret = is31fl3736_set_led_state(led_data->priv,
led_data->cs_ch, led_data->sw_ch, state);
if (!ret)
led_data->cur_state = state;
mutex_unlock(&led_data->priv->mutex);
return size;
}
static ssize_t is31fl3736_led_show_time(struct device *dev,
struct device_attribute *attr, char *buf)
{
struct led_classdev *led_cdev = dev_get_drvdata(dev);
struct is31fl3736_led_data *led_data =
container_of(led_cdev, struct is31fl3736_led_data, cdev);
return snprintf(buf, PAGE_SIZE, "%d %d %d %d\n",
led_data->rise_time, led_data->hold_time,
led_data->fall_time, led_data->off_time);
}
static ssize_t is31fl3736_led_store_time(struct device *dev,
struct device_attribute *attr,
const char *buf, size_t size)
{
struct led_classdev *led_cdev = dev_get_drvdata(dev);
struct is31fl3736_led_data *led_data =
container_of(led_cdev, struct is31fl3736_led_data, cdev);
int ret, rise_time, hold_time, fall_time, off_time;
ret = sscanf(buf, "%d %d %d %d",
&rise_time, &hold_time,
&fall_time, &off_time);
pr_debug("%s, rise: %d, hold: %d, fall: %d, off: %d\n",
__func__, rise_time, hold_time, fall_time, off_time);
if (led_data->cur_mode == IS31FL3736_PWM_MODE) {
dev_info(led_cdev->dev,
"%s: current mode is PWM mode\n", __func__);
return -EPERM;
}
if ((rise_time < 0 || rise_time >= IS31FL3736_t1_t3_time_MAX) ||
(hold_time < 0 || hold_time >= IS31FL3736_t2_time_MAX) ||
(fall_time < 0 || fall_time >= IS31FL3736_t1_t3_time_MAX) ||
(off_time < 0 || off_time >= IS31FL3736_t4_time_MAX)) {
dev_info(led_cdev->dev,
"%s: breath time paras invalid\n", __func__);
return -EINVAL;
}
mutex_lock(&led_data->priv->mutex);
ret = is31fl3736_set_breath_time(led_data->priv,
rise_time, hold_time,
fall_time, off_time,
led_data->cur_mode);
if (!ret) {
led_data->rise_time = rise_time;/*t1, fade in*/
led_data->hold_time = hold_time;/*t2, on*/
led_data->fall_time = fall_time;/*t3, fade out*/
led_data->off_time = off_time; /*t4, off*/
}
mutex_unlock(&led_data->priv->mutex);
return size;
}
static ssize_t is31fl3736_led_show_loop(struct device *dev,
struct device_attribute *attr, char *buf)
{
struct led_classdev *led_cdev = dev_get_drvdata(dev);
struct is31fl3736_led_data *led_data =
container_of(led_cdev, struct is31fl3736_led_data, cdev);
return sprintf(buf, "%d\n", led_data->loop);
}
static ssize_t is31fl3736_led_store_loop(struct device *dev,
struct device_attribute *attr,
const char *buf, size_t size)
{
struct led_classdev *led_cdev = dev_get_drvdata(dev);
struct is31fl3736_led_data *led_data =
container_of(led_cdev, struct is31fl3736_led_data, cdev);
unsigned long loop;
int ret;
if (kstrtoul(buf, 10, &loop))
return -EINVAL;
if (loop < 0)
loop = 0;
pr_debug("%s, set breath loop time: %ld", __func__, loop);
mutex_lock(&led_data->priv->mutex);
ret = is31fl3736_set_breath_loop(led_data->priv,
(u32)loop, led_data->cur_mode);
if (!ret)
led_data->loop = (u32)loop;
mutex_unlock(&led_data->priv->mutex);
return size;
}
static ssize_t is31fl3736_led_show_blink(struct device *dev,
struct device_attribute *attr, char *buf)
{
return 0;
}
static ssize_t is31fl3736_led_store_blink(struct device *dev,
struct device_attribute *attr,
const char *buf, size_t size)
{
struct led_classdev *led_cdev = dev_get_drvdata(dev);
struct is31fl3736_led_data *led_data =
container_of(led_cdev, struct is31fl3736_led_data, cdev);
unsigned long blink;
int ret;
ret = kstrtoul(buf, 10, &blink);
if (ret)
return ret;
pr_debug("%s, blink time: %ld", __func__, blink);
mutex_lock(&led_data->priv->mutex);
is31fl3736_set_breath_blink(led_data->priv, (int)blink);
mutex_unlock(&led_data->priv->mutex);
return size;
}
static IS31Fl3736_ATTR_RW(mode);
static IS31Fl3736_ATTR_RW(state);
static IS31Fl3736_ATTR_RW(time);
static IS31Fl3736_ATTR_RW(loop);
static IS31Fl3736_ATTR_RW(blink);
static struct attribute *is31fl3736_led_attributes[] = {
&dev_attr_mode.attr,
&dev_attr_state.attr,
&dev_attr_time.attr,
&dev_attr_loop.attr,
&dev_attr_blink.attr,
NULL,
};
static struct attribute_group is31fl3736_led_attribute_group = {
.attrs = is31fl3736_led_attributes
};
static const struct attribute_group *is31fl3736_led_attribute_groups[] = {
&is31fl3736_led_attribute_group,
NULL
};
static inline size_t sizeof_is31fl3736_priv(int num_leds)
{
return sizeof(struct is31fl3736_priv) +
(sizeof(struct is31fl3736_led_data) * num_leds);
}
static int is31fl3736_parse_child_dt(const struct device *dev,
const struct device_node *child,
struct is31fl3736_led_data *led_data)
{
struct led_classdev *cdev = &led_data->cdev;
int ret = 0;
u32 reg;
u32 sw, cs;
if (of_property_read_string(child, "label", &cdev->name))
cdev->name = child->name;
ret = of_property_read_u32(child, "reg", &reg);
if (ret || reg < 1) {
dev_err(dev,
"Child node %s does not have a valid reg property\n",
child->full_name);
return -EINVAL;
}
led_data->channel = reg;
pr_debug("%s, line:%d, led channel: %d, name: %s\n",
__func__, __LINE__, reg, child->name);
ret = of_property_read_u32(child, "sw_location", &sw);
if (ret || sw < 1 || sw > led_data->priv->sw_max) {
dev_err(dev,
"Child node %s does not have a valid sw location property\n",
child->full_name);
return -EINVAL;
}
led_data->sw_ch = sw;
ret = of_property_read_u32(child, "cs_location", &cs);
if (ret || cs < 1 || cs > led_data->priv->cs_max) {
dev_err(dev,
"Child node %s does not have a valid cs location property\n",
child->full_name);
return -EINVAL;
}
led_data->cs_ch = cs;
of_property_read_string(child, "linux,default-trigger",
&cdev->default_trigger);
cdev->brightness_set_blocking = is31fl3736_brightness_set;
cdev->brightness = LED_OFF;
cdev->groups = is31fl3736_led_attribute_groups;
return 0;
}
static struct is31fl3736_led_data *is31fl3736_find_led_data(
struct is31fl3736_priv *priv,
u8 channel)
{
size_t i;
for (i = 0; i < priv->num_leds; i++) {
if (priv->leds[i].channel == channel)
return &priv->leds[i];
}
return NULL;
}
static int is31fl3736_parse_dt(struct device *dev,
struct is31fl3736_priv *priv)
{
struct device_node *child;
struct gpio_config config;
int ret = 0;
/*gpio power enable*/
priv->power_gpio = of_get_named_gpio_flags(dev->of_node,
"gpio-power", 0,
(enum of_gpio_flags *)&config);
pr_debug("%s, line:%d, power_gpio: %d!\n",
__func__, __LINE__, priv->power_gpio);
/*gpio shdn enable*/
priv->shdn_gpio = of_get_named_gpio_flags(dev->of_node,
"gpio-shdn", 0,
(enum of_gpio_flags *)&config);
pr_debug("%s, line:%d, shdn_gpio: %d!\n",
__func__, __LINE__, priv->shdn_gpio);
for_each_child_of_node(dev->of_node, child) {
struct is31fl3736_led_data *led_data =
&priv->leds[priv->num_leds];
const struct is31fl3736_led_data *other_led_data;
led_data->priv = priv;
ret = is31fl3736_parse_child_dt(dev, child, led_data);
if (ret)
goto err;
/* Detect if channel is already in use by another child */
other_led_data = is31fl3736_find_led_data(priv,
led_data->channel);
if (other_led_data) {
dev_err(dev,
"%s and %s both attempting to use channel %d\n",
led_data->cdev.name,
other_led_data->cdev.name,
led_data->channel);
goto err;
}
ret = devm_led_classdev_register(dev, &led_data->cdev);
if (ret) {
dev_err(dev, "failed to register PWM led for %s: %d\n",
led_data->cdev.name, ret);
goto err;
}
priv->num_leds++;
}
return 0;
err:
of_node_put(child);
return ret;
}
static const struct of_device_id of_is31fl3736_match[] = {
{ .compatible = "issi,is31fl3736", },
{},
};
MODULE_DEVICE_TABLE(of, of_is31fl3736_match);
static int is31fl3736_probe(struct i2c_client *client,
const struct i2c_device_id *id)
{
const struct of_device_id *of_dev_id;
struct device *dev = &client->dev;
struct is31fl3736_priv *priv;
int count;
int ret = 0;
pr_debug("%s, line:%d, i2c device id: %s!\n",
__func__, __LINE__, id->name);
of_dev_id = of_match_device(of_is31fl3736_match, dev);
if (!of_dev_id)
return -EINVAL;
count = of_get_child_count(dev->of_node);
if (!count)
return -EINVAL;
pr_debug("%s, line:%d, led count: %d!\n", __func__, __LINE__, count);
priv = devm_kzalloc(dev, sizeof_is31fl3736_priv(count),
GFP_KERNEL);
if (!priv)
return -ENOMEM;
priv->client = client;
priv->cs_max = IS31FL3736_CSX_MAX;
priv->sw_max = IS31FL3736_SWY_MAX;
i2c_set_clientdata(client, priv);
ret = is31fl3736_parse_dt(dev, priv);
if (ret)
return ret;
if (gpio_is_valid(priv->shdn_gpio)) {
ret = gpio_request(priv->shdn_gpio, "shdn gpio");
if (!ret) {
gpio_direction_output(priv->shdn_gpio, 1);
gpio_set_value(priv->shdn_gpio, 1);
pr_info("%s, line:%d, enable shdn gpio: %d!\n",
__func__, __LINE__, priv->shdn_gpio);
msleep(20);
} else {
pr_err("%s, line:%d, failed request shdn gpio: %d!\n",
__func__, __LINE__,
priv->shdn_gpio);
}
}
if (gpio_is_valid(priv->power_gpio)) {
ret = gpio_request(priv->power_gpio, "power gpio");
if (!ret) {
gpio_direction_output(priv->power_gpio, 1);
gpio_set_value(priv->power_gpio, 1);
pr_info("%s, line:%d, power gpio: %d!\n",
__func__, __LINE__, priv->power_gpio);
msleep(20);
} else {
pr_err("%s, line:%d, failed request power gpio: %d!\n",
__func__, __LINE__,
priv->power_gpio);
}
}
mutex_init(&priv->mutex);
is31fl3736_reset_regs(priv);
is31fl3736_init_regs(priv);
pr_info("%s, line:%d, is31fl3736_probe succeed!\n",
__func__, __LINE__);
return 0;
}
static int is31fl3736_remove(struct i2c_client *client)
{
struct is31fl3736_priv *priv = i2c_get_clientdata(client);
return is31fl3736_reset_regs(priv);
}
/*
* i2c-core (and modalias) requires that id_table be properly filled,
* even though it is not used for DeviceTree based instantiation.
*/
static const struct i2c_device_id is31fl3736_id[] = {
{ "is31fl3736" },
{},
};
MODULE_DEVICE_TABLE(i2c, is31fl3736_id);
static struct i2c_driver is31fl3736_driver = {
.driver = {
.name = "is31fl3736",
.of_match_table = of_is31fl3736_match,
},
.probe = is31fl3736_probe,
.remove = is31fl3736_remove,
.id_table = is31fl3736_id,
};
module_i2c_driver(is31fl3736_driver);
MODULE_AUTHOR("xudong<xudong@allwinnertech.com>");
MODULE_DESCRIPTION("ISSI IS31FL3736 LED driver");
MODULE_LICENSE("GPL v2");