diff -drupN a/drivers/leds/leds-is31fl3736.c b/drivers/leds/leds-is31fl3736.c --- a/drivers/leds/leds-is31fl3736.c 1970-01-01 03:00:00.000000000 +0300 +++ b/drivers/leds/leds-is31fl3736.c 2022-06-12 05:28:14.000000000 +0300 @@ -0,0 +1,1065 @@ +/* + * 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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#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, ®); + 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", ®); + 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"); +MODULE_DESCRIPTION("ISSI IS31FL3736 LED driver"); +MODULE_LICENSE("GPL v2");