firmware/br-ext-chip-allwinner/board/v83x/kernel/patches/00000-drivers_leds_leds-is3...

1070 lines
27 KiB
Diff

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 <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");