mirror of https://github.com/OpenIPC/firmware.git
1070 lines
27 KiB
Diff
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, ®);
|
|
+ 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<xudong@allwinnertech.com>");
|
|
+MODULE_DESCRIPTION("ISSI IS31FL3736 LED driver");
|
|
+MODULE_LICENSE("GPL v2");
|