@@ -244,6 +244,13 @@ config LEDS_MT6323
This option enables support for on-chip LED drivers found on
Mediatek MT6323 PMIC.
+config LEDS_MP3326
+ tristate "LED Support for Monolithic power system MP3326"
+ depends on LEDS_CLASS
+ help
+ This option enables support for on-chip LED drivers found on
+ Monolithic power system MP3326.
+
config LEDS_S3C24XX
tristate "LED Support for Samsung S3C24XX GPIO LEDs"
depends on LEDS_CLASS
@@ -62,6 +62,7 @@ obj-$(CONFIG_LEDS_MIKROTIK_RB532) += leds-rb532.o
obj-$(CONFIG_LEDS_MLXCPLD) += leds-mlxcpld.o
obj-$(CONFIG_LEDS_MLXREG) += leds-mlxreg.o
obj-$(CONFIG_LEDS_MT6323) += leds-mt6323.o
+obj-$(CONFIG_LEDS_MP3326) += leds-mp3326.o
obj-$(CONFIG_LEDS_NET48XX) += leds-net48xx.o
obj-$(CONFIG_LEDS_NETXBIG) += leds-netxbig.o
obj-$(CONFIG_LEDS_NIC78BX) += leds-nic78bx.o
new file mode 100644
@@ -0,0 +1,836 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ *
+ * MP3326 Led driver
+ *
+ * Copyright 2023 Monolithic Power Systems, Inc
+ *
+ * Author: Yuxi Wang <Yuxi.Wang@monolithicpower.com>
+ */
+#include <linux/bits.h>
+#include <linux/module.h>
+#include <linux/mod_devicetable.h>
+#include <linux/platform_device.h>
+#include <linux/regmap.h>
+#include <linux/i2c.h>
+#include <linux/of.h>
+#include <linux/slab.h>
+#include <linux/interrupt.h>
+#include <linux/mutex.h>
+#include <linux/leds.h>
+
+/*reg list*/
+#define MP3326_PWM_DIM_FREQUENCY_CONFIG 0x00
+#define MP3326_PWM_CTRL 0x01
+#define MP3326_PWM_DIM_FREQUENCY_CONFIG 0x00
+#define MP3326_PWM_CTRL_CHANNEL_9_16 0x04
+#define MP3326_PWM_CTRL_CHANNEL_1_8 0x05
+#define MP3326_PWM_OPEN_FAULT_CHANNEL_9_16 0x06
+#define MP3326_PWM_OPEN_FAULT_CHANNEL_1_8 0x07
+#define MP3326_PWM_SHORT_FAULT_CHANNEL_9_16 0x08
+#define MP3326_PWM_SHORT_FAULT_CHANNEL_1_8 0x09
+#define MP3326_PWM_CURRENT_SET_CHANNEL1 0x0A
+#define MP3326_PWM_DUTY_LSB_SET_CHANNEL1 0x0B
+#define MP3326_PWM_DUTY_MSB_SET_CHANNEL1 0x0C
+#define MP3326_PWM_CURRENT_SET_CHANNEL2 0x0D
+#define MP3326_PWM_DUTY_LSB_SET_CHANNEL2 0x0E
+#define MP3326_PWM_DUTY_MSB_SET_CHANNEL2 0x0F
+#define MP3326_PWM_CURRENT_SET_CHANNEL3 0x10
+#define MP3326_PWM_DUTY_LSB_SET_CHANNEL3 0x11
+#define MP3326_PWM_DUTY_MSB_SET_CHANNEL3 0x12
+#define MP3326_PWM_CURRENT_SET_CHANNEL4 0x13
+#define MP3326_PWM_DUTY_LSB_SET_CHANNEL4 0x14
+#define MP3326_PWM_DUTY_MSB_SET_CHANNEL4 0x15
+#define MP3326_PWM_CURRENT_SET_CHANNEL5 0x16
+#define MP3326_PWM_DUTY_LSB_SET_CHANNEL5 0x17
+#define MP3326_PWM_DUTY_MSB_SET_CHANNEL5 0x18
+#define MP3326_PWM_CURRENT_SET_CHANNEL6 0x19
+#define MP3326_PWM_DUTY_LSB_SET_CHANNEL6 0x1A
+#define MP3326_PWM_DUTY_MSB_SET_CHANNEL6 0x1B
+#define MP3326_PWM_CURRENT_SET_CHANNEL7 0x1C
+#define MP3326_PWM_DUTY_LSB_SET_CHANNEL7 0x1D
+#define MP3326_PWM_DUTY_MSB_SET_CHANNEL7 0x1E
+#define MP3326_PWM_CURRENT_SET_CHANNEL8 0x1F
+#define MP3326_PWM_DUTY_LSB_SET_CHANNEL8 0x20
+#define MP3326_PWM_DUTY_MSB_SET_CHANNEL8 0x21
+#define MP3326_PWM_CURRENT_SET_CHANNEL9 0x22
+#define MP3326_PWM_DUTY_LSB_SET_CHANNEL9 0x23
+#define MP3326_PWM_DUTY_MSB_SET_CHANNEL9 0x24
+#define MP3326_PWM_CURRENT_SET_CHANNEL10 0x25
+#define MP3326_PWM_DUTY_LSB_SET_CHANNEL10 0x26
+#define MP3326_PWM_DUTY_MSB_SET_CHANNEL10 0x27
+#define MP3326_PWM_CURRENT_SET_CHANNEL11 0x28
+#define MP3326_PWM_DUTY_LSB_SET_CHANNEL11 0x29
+#define MP3326_PWM_DUTY_MSB_SET_CHANNEL11 0x2A
+#define MP3326_PWM_CURRENT_SET_CHANNEL12 0x2B
+#define MP3326_PWM_DUTY_LSB_SET_CHANNEL12 0x2C
+#define MP3326_PWM_DUTY_MSB_SET_CHANNEL12 0x2D
+#define MP3326_PWM_CURRENT_SET_CHANNEL13 0x2E
+#define MP3326_PWM_DUTY_LSB_SET_CHANNEL13 0x2F
+#define MP3326_PWM_DUTY_MSB_SET_CHANNEL13 0x30
+#define MP3326_PWM_CURRENT_SET_CHANNEL14 0x31
+#define MP3326_PWM_DUTY_LSB_SET_CHANNEL14 0x32
+#define MP3326_PWM_DUTY_MSB_SET_CHANNEL14 0x33
+#define MP3326_PWM_CURRENT_SET_CHANNEL15 0x34
+#define MP3326_PWM_DUTY_LSB_SET_CHANNEL15 0x35
+#define MP3326_PWM_DUTY_MSB_SET_CHANNEL15 0x36
+#define MP3326_PWM_CURRENT_SET_CHANNEL16 0x37
+#define MP3326_PWM_DUTY_LSB_SET_CHANNEL16 0x38
+#define MP3326_PWM_DUTY_MSB_SET_CHANNEL16 0x39
+
+#define MP3326_REG_FIELD(ref) { \
+ .regmap_fields = ref##_reg_fields,\
+}
+
+enum MP3326_Channel {
+ Channel1,
+ Channel2,
+ Channel3,
+ Channel4,
+ Channel5,
+ Channel6,
+ Channel7,
+ Channel8,
+ Channel9,
+ Channel10,
+ Channel11,
+ Channel12,
+ Channel13,
+ Channel14,
+ Channel15,
+ Channel16,
+ Max_Channel,
+};
+
+enum RGB_CTRL {
+ENABLE,
+BRIGHTNESS,
+COLOR_H4,
+COLOR_L8,
+OPEN_FAULT,
+SHORT_FAULT,
+Max_CTRL,
+};
+
+struct MP3326_reg_field {
+ struct reg_field *regmap_fields;
+};
+
+static struct reg_field channel1_reg_fields[] = {
+ [ENABLE] = REG_FIELD(MP3326_PWM_CTRL_CHANNEL_1_8, 0, 0),
+ [BRIGHTNESS] = REG_FIELD(MP3326_PWM_CURRENT_SET_CHANNEL1, 0, 5),
+ [COLOR_H4] = REG_FIELD(MP3326_PWM_DUTY_LSB_SET_CHANNEL1, 0, 3),
+ [COLOR_L8] = REG_FIELD(MP3326_PWM_DUTY_MSB_SET_CHANNEL1, 0, 7),
+ [OPEN_FAULT] = REG_FIELD(MP3326_PWM_OPEN_FAULT_CHANNEL_1_8, 0, 0),
+ [SHORT_FAULT] = REG_FIELD(MP3326_PWM_SHORT_FAULT_CHANNEL_1_8, 0, 0),
+};
+
+static struct reg_field channel2_reg_fields[] = {
+ [ENABLE] = REG_FIELD(MP3326_PWM_CTRL_CHANNEL_1_8, 1, 1),
+ [BRIGHTNESS] = REG_FIELD(MP3326_PWM_CURRENT_SET_CHANNEL2, 0, 5),
+ [COLOR_H4] = REG_FIELD(MP3326_PWM_DUTY_LSB_SET_CHANNEL2, 0, 3),
+ [COLOR_L8] = REG_FIELD(MP3326_PWM_DUTY_MSB_SET_CHANNEL2, 0, 7),
+ [OPEN_FAULT] = REG_FIELD(MP3326_PWM_OPEN_FAULT_CHANNEL_1_8, 1, 1),
+ [SHORT_FAULT] = REG_FIELD(MP3326_PWM_SHORT_FAULT_CHANNEL_1_8, 1, 1),
+};
+
+static struct reg_field channel3_reg_fields[] = {
+ [ENABLE] = REG_FIELD(MP3326_PWM_CTRL_CHANNEL_1_8, 2, 2),
+ [BRIGHTNESS] = REG_FIELD(MP3326_PWM_CURRENT_SET_CHANNEL3, 0, 5),
+ [COLOR_H4] = REG_FIELD(MP3326_PWM_DUTY_LSB_SET_CHANNEL3, 0, 3),
+ [COLOR_L8] = REG_FIELD(MP3326_PWM_DUTY_MSB_SET_CHANNEL3, 0, 7),
+ [OPEN_FAULT] = REG_FIELD(MP3326_PWM_OPEN_FAULT_CHANNEL_1_8, 2, 2),
+ [SHORT_FAULT] = REG_FIELD(MP3326_PWM_SHORT_FAULT_CHANNEL_1_8, 2, 2),
+};
+
+static struct reg_field channel4_reg_fields[] = {
+ [ENABLE] = REG_FIELD(MP3326_PWM_CTRL_CHANNEL_1_8, 3, 3),
+ [BRIGHTNESS] = REG_FIELD(MP3326_PWM_CURRENT_SET_CHANNEL4, 0, 5),
+ [COLOR_H4] = REG_FIELD(MP3326_PWM_DUTY_LSB_SET_CHANNEL4, 0, 3),
+ [COLOR_L8] = REG_FIELD(MP3326_PWM_DUTY_MSB_SET_CHANNEL4, 0, 7),
+ [OPEN_FAULT] = REG_FIELD(MP3326_PWM_OPEN_FAULT_CHANNEL_1_8, 3, 3),
+ [SHORT_FAULT] = REG_FIELD(MP3326_PWM_SHORT_FAULT_CHANNEL_1_8, 3, 3),
+};
+
+static struct reg_field channel5_reg_fields[] = {
+ [ENABLE] = REG_FIELD(MP3326_PWM_CTRL_CHANNEL_1_8, 4, 4),
+ [BRIGHTNESS] = REG_FIELD(MP3326_PWM_CURRENT_SET_CHANNEL5, 0, 5),
+ [COLOR_H4] = REG_FIELD(MP3326_PWM_DUTY_LSB_SET_CHANNEL5, 0, 3),
+ [COLOR_L8] = REG_FIELD(MP3326_PWM_DUTY_MSB_SET_CHANNEL5, 0, 7),
+ [OPEN_FAULT] = REG_FIELD(MP3326_PWM_OPEN_FAULT_CHANNEL_1_8, 4, 4),
+ [SHORT_FAULT] = REG_FIELD(MP3326_PWM_SHORT_FAULT_CHANNEL_1_8, 4, 4),
+};
+
+static struct reg_field channel6_reg_fields[] = {
+ [ENABLE] = REG_FIELD(MP3326_PWM_CTRL_CHANNEL_1_8, 5, 5),
+ [BRIGHTNESS] = REG_FIELD(MP3326_PWM_CURRENT_SET_CHANNEL6, 0, 5),
+ [COLOR_H4] = REG_FIELD(MP3326_PWM_DUTY_LSB_SET_CHANNEL6, 0, 3),
+ [COLOR_L8] = REG_FIELD(MP3326_PWM_DUTY_MSB_SET_CHANNEL6, 0, 7),
+ [OPEN_FAULT] = REG_FIELD(MP3326_PWM_OPEN_FAULT_CHANNEL_1_8, 5, 5),
+ [SHORT_FAULT] = REG_FIELD(MP3326_PWM_SHORT_FAULT_CHANNEL_1_8, 5, 5),
+};
+
+static struct reg_field channel7_reg_fields[] = {
+ [ENABLE] = REG_FIELD(MP3326_PWM_CTRL_CHANNEL_1_8, 6, 6),
+ [BRIGHTNESS] = REG_FIELD(MP3326_PWM_CURRENT_SET_CHANNEL7, 0, 5),
+ [COLOR_H4] = REG_FIELD(MP3326_PWM_DUTY_LSB_SET_CHANNEL7, 0, 3),
+ [COLOR_L8] = REG_FIELD(MP3326_PWM_DUTY_MSB_SET_CHANNEL7, 0, 7),
+ [OPEN_FAULT] = REG_FIELD(MP3326_PWM_OPEN_FAULT_CHANNEL_1_8, 6, 6),
+ [SHORT_FAULT] = REG_FIELD(MP3326_PWM_SHORT_FAULT_CHANNEL_1_8, 6, 6),
+};
+
+static struct reg_field channel8_reg_fields[] = {
+ [ENABLE] = REG_FIELD(MP3326_PWM_CTRL_CHANNEL_1_8, 7, 7),
+ [BRIGHTNESS] = REG_FIELD(MP3326_PWM_CURRENT_SET_CHANNEL8, 0, 5),
+ [COLOR_H4] = REG_FIELD(MP3326_PWM_DUTY_LSB_SET_CHANNEL8, 0, 3),
+ [COLOR_L8] = REG_FIELD(MP3326_PWM_DUTY_MSB_SET_CHANNEL8, 0, 7),
+ [OPEN_FAULT] = REG_FIELD(MP3326_PWM_OPEN_FAULT_CHANNEL_1_8, 7, 7),
+ [SHORT_FAULT] = REG_FIELD(MP3326_PWM_SHORT_FAULT_CHANNEL_1_8, 7, 7),
+};
+
+static struct reg_field channel9_reg_fields[] = {
+ [ENABLE] = REG_FIELD(MP3326_PWM_CTRL_CHANNEL_9_16, 0, 0),
+ [BRIGHTNESS] = REG_FIELD(MP3326_PWM_CURRENT_SET_CHANNEL9, 0, 5),
+ [COLOR_H4] = REG_FIELD(MP3326_PWM_DUTY_LSB_SET_CHANNEL9, 0, 3),
+ [COLOR_L8] = REG_FIELD(MP3326_PWM_DUTY_MSB_SET_CHANNEL9, 0, 7),
+ [OPEN_FAULT] = REG_FIELD(MP3326_PWM_OPEN_FAULT_CHANNEL_9_16, 0, 0),
+ [SHORT_FAULT] = REG_FIELD(MP3326_PWM_SHORT_FAULT_CHANNEL_9_16, 0, 0),
+};
+
+static struct reg_field channel10_reg_fields[] = {
+ [ENABLE] = REG_FIELD(MP3326_PWM_CTRL_CHANNEL_9_16, 1, 1),
+ [BRIGHTNESS] = REG_FIELD(MP3326_PWM_CURRENT_SET_CHANNEL10, 0, 5),
+ [COLOR_H4] = REG_FIELD(MP3326_PWM_DUTY_LSB_SET_CHANNEL10, 0, 3),
+ [COLOR_L8] = REG_FIELD(MP3326_PWM_DUTY_MSB_SET_CHANNEL10, 0, 7),
+ [OPEN_FAULT] = REG_FIELD(MP3326_PWM_OPEN_FAULT_CHANNEL_9_16, 1, 1),
+ [SHORT_FAULT] = REG_FIELD(MP3326_PWM_SHORT_FAULT_CHANNEL_9_16, 1, 1),
+};
+
+static struct reg_field channel11_reg_fields[] = {
+ [ENABLE] = REG_FIELD(MP3326_PWM_CTRL_CHANNEL_9_16, 2, 2),
+ [BRIGHTNESS] = REG_FIELD(MP3326_PWM_CURRENT_SET_CHANNEL11, 0, 5),
+ [COLOR_H4] = REG_FIELD(MP3326_PWM_DUTY_LSB_SET_CHANNEL11, 0, 3),
+ [COLOR_L8] = REG_FIELD(MP3326_PWM_DUTY_MSB_SET_CHANNEL11, 0, 7),
+ [OPEN_FAULT] = REG_FIELD(MP3326_PWM_OPEN_FAULT_CHANNEL_9_16, 2, 2),
+ [SHORT_FAULT] = REG_FIELD(MP3326_PWM_SHORT_FAULT_CHANNEL_9_16, 2, 2),
+};
+
+static struct reg_field channel12_reg_fields[] = {
+ [ENABLE] = REG_FIELD(MP3326_PWM_CTRL_CHANNEL_9_16, 3, 3),
+ [BRIGHTNESS] = REG_FIELD(MP3326_PWM_CURRENT_SET_CHANNEL12, 0, 5),
+ [COLOR_H4] = REG_FIELD(MP3326_PWM_DUTY_LSB_SET_CHANNEL12, 0, 3),
+ [COLOR_L8] = REG_FIELD(MP3326_PWM_DUTY_MSB_SET_CHANNEL12, 0, 7),
+ [OPEN_FAULT] = REG_FIELD(MP3326_PWM_OPEN_FAULT_CHANNEL_9_16, 3, 3),
+ [SHORT_FAULT] = REG_FIELD(MP3326_PWM_SHORT_FAULT_CHANNEL_9_16, 3, 3),
+};
+
+static struct reg_field channel13_reg_fields[] = {
+ [ENABLE] = REG_FIELD(MP3326_PWM_CTRL_CHANNEL_9_16, 4, 4),
+ [BRIGHTNESS] = REG_FIELD(MP3326_PWM_CURRENT_SET_CHANNEL13, 0, 5),
+ [COLOR_H4] = REG_FIELD(MP3326_PWM_DUTY_LSB_SET_CHANNEL13, 0, 3),
+ [COLOR_L8] = REG_FIELD(MP3326_PWM_DUTY_MSB_SET_CHANNEL13, 0, 7),
+ [OPEN_FAULT] = REG_FIELD(MP3326_PWM_OPEN_FAULT_CHANNEL_9_16, 4, 4),
+ [SHORT_FAULT] = REG_FIELD(MP3326_PWM_SHORT_FAULT_CHANNEL_9_16, 4, 4),
+};
+
+static struct reg_field channel14_reg_fields[] = {
+ [ENABLE] = REG_FIELD(MP3326_PWM_CTRL_CHANNEL_9_16, 5, 5),
+ [BRIGHTNESS] = REG_FIELD(MP3326_PWM_CURRENT_SET_CHANNEL14, 0, 5),
+ [COLOR_H4] = REG_FIELD(MP3326_PWM_DUTY_LSB_SET_CHANNEL14, 0, 3),
+ [COLOR_L8] = REG_FIELD(MP3326_PWM_DUTY_MSB_SET_CHANNEL14, 0, 7),
+ [OPEN_FAULT] = REG_FIELD(MP3326_PWM_OPEN_FAULT_CHANNEL_9_16, 5, 5),
+ [SHORT_FAULT] = REG_FIELD(MP3326_PWM_SHORT_FAULT_CHANNEL_9_16, 5, 5),
+};
+
+static struct reg_field channel15_reg_fields[] = {
+ [ENABLE] = REG_FIELD(MP3326_PWM_CTRL_CHANNEL_9_16, 6, 6),
+ [BRIGHTNESS] = REG_FIELD(MP3326_PWM_CURRENT_SET_CHANNEL15, 0, 5),
+ [COLOR_H4] = REG_FIELD(MP3326_PWM_DUTY_LSB_SET_CHANNEL15, 0, 3),
+ [COLOR_L8] = REG_FIELD(MP3326_PWM_DUTY_MSB_SET_CHANNEL15, 0, 7),
+ [OPEN_FAULT] = REG_FIELD(MP3326_PWM_OPEN_FAULT_CHANNEL_9_16, 6, 6),
+ [SHORT_FAULT] = REG_FIELD(MP3326_PWM_SHORT_FAULT_CHANNEL_9_16, 6, 6),
+};
+
+static struct reg_field channel16_reg_fields[] = {
+ [ENABLE] = REG_FIELD(MP3326_PWM_CTRL_CHANNEL_9_16, 7, 7),
+ [BRIGHTNESS] = REG_FIELD(MP3326_PWM_CURRENT_SET_CHANNEL16, 0, 5),
+ [COLOR_H4] = REG_FIELD(MP3326_PWM_DUTY_LSB_SET_CHANNEL16, 0, 3),
+ [COLOR_L8] = REG_FIELD(MP3326_PWM_DUTY_MSB_SET_CHANNEL16, 0, 7),
+ [OPEN_FAULT] = REG_FIELD(MP3326_PWM_OPEN_FAULT_CHANNEL_9_16, 7, 7),
+ [SHORT_FAULT] = REG_FIELD(MP3326_PWM_SHORT_FAULT_CHANNEL_9_16, 7, 7),
+};
+
+static const struct MP3326_reg_field MP3266_reg_fields[] = {
+ [Channel1] = MP3326_REG_FIELD(channel1),
+ [Channel2] = MP3326_REG_FIELD(channel2),
+ [Channel3] = MP3326_REG_FIELD(channel3),
+ [Channel4] = MP3326_REG_FIELD(channel4),
+ [Channel5] = MP3326_REG_FIELD(channel5),
+ [Channel6] = MP3326_REG_FIELD(channel6),
+ [Channel7] = MP3326_REG_FIELD(channel7),
+ [Channel8] = MP3326_REG_FIELD(channel8),
+ [Channel9] = MP3326_REG_FIELD(channel9),
+ [Channel10] = MP3326_REG_FIELD(channel10),
+ [Channel11] = MP3326_REG_FIELD(channel11),
+ [Channel12] = MP3326_REG_FIELD(channel12),
+ [Channel13] = MP3326_REG_FIELD(channel13),
+ [Channel14] = MP3326_REG_FIELD(channel14),
+ [Channel15] = MP3326_REG_FIELD(channel15),
+ [Channel16] = MP3326_REG_FIELD(channel16),
+};
+
+struct RGB {
+struct led_classdev cdev;
+struct MP3326_Led *chip;
+int red;
+int green;
+int blue;
+int AnalogDim;
+int PWMDim;
+bool IsAlive;
+};
+
+static const struct regmap_config MP3326_regmap_config = {
+.reg_bits = 8,
+.val_bits = 8,
+};
+
+struct ChannelField {
+ struct regmap_field *regmap_fields[Max_CTRL];
+};
+
+struct MP3326_Led {
+struct i2c_client *client;
+struct regmap *regmap;
+struct ChannelField *channel_fields[Max_Channel];
+struct mutex mutex;
+u32 num_rgbs;
+struct RGB rgbs[];
+};
+
+/*
+ * Set the brightness.
+ */
+static int MP3326_led_set(struct led_classdev *led_cdev,
+ enum led_brightness value)
+{
+ struct RGB *led = container_of(led_cdev, struct RGB, cdev);
+ struct MP3326_Led *chip = led->chip;
+ int ret;
+
+ if (value > 63)
+ led->AnalogDim = 63;
+ else
+ led->AnalogDim = value;
+
+ ret = regmap_field_write(chip->channel_fields[led->red]->regmap_fields[BRIGHTNESS], led->AnalogDim);
+ if (ret)
+ return -EINVAL;
+
+ ret = regmap_field_write(chip->channel_fields[led->green]->regmap_fields[BRIGHTNESS], led->AnalogDim);
+ if (ret)
+ return -EINVAL;
+
+ ret = regmap_field_write(chip->channel_fields[led->blue]->regmap_fields[BRIGHTNESS], led->AnalogDim);
+ if (ret)
+ return -EINVAL;
+
+ return 0;
+}
+
+/*
+ * Show the current pwm dim value which is decimal.
+ */
+static ssize_t pwm_Dim_show(struct device *dev,
+ struct device_attribute *attr,
+ char *buf)
+{
+ struct led_classdev *cdev = dev_get_drvdata(dev);
+ struct RGB *led = container_of(cdev, struct RGB, cdev);
+ struct MP3326_Led *chip = led->chip;
+ unsigned int val;
+ int rval = 0, gval = 0, bval = 0;
+
+ regmap_field_read(chip->channel_fields[led->red]->regmap_fields[COLOR_L8], &val);
+ rval |= val << 4;
+ regmap_field_read(chip->channel_fields[led->red]->regmap_fields[COLOR_H4], &val);
+ rval |= val;
+ val = 0;
+ regmap_field_read(chip->channel_fields[led->green]->regmap_fields[COLOR_L8], &val);
+ gval |= val << 4;
+ regmap_field_read(chip->channel_fields[led->green]->regmap_fields[COLOR_H4], &val);
+ gval |= val;
+ val = 0;
+ regmap_field_read(chip->channel_fields[led->blue]->regmap_fields[COLOR_L8], &val);
+ bval |= val << 4;
+ regmap_field_read(chip->channel_fields[led->blue]->regmap_fields[COLOR_H4], &val);
+ bval |= val;
+
+ rval = rval * 255 / 4095 + ((rval * 255) % 4095 > 2047 ? 1 : 0);
+ gval = gval * 255 / 4095 + ((gval * 255) % 4095 > 2047 ? 1 : 0);
+ bval = bval * 255 / 4095 + ((bval * 255) % 4095 > 2047 ? 1 : 0);
+
+
+ return sysfs_emit(buf, "%d %d %d\n", rval, gval, bval);
+}
+
+/*
+ * PWM dimming, here are two input ways.
+ * The first: Input '200 200 200' which sequence is red green blue and decimal.
+ * The Second: Input 'H 0f 0f 0f' which sequence is red green blue and Hex.
+ */
+static ssize_t pwm_Dim_store(struct device *dev,
+ struct device_attribute *attr,
+ const char *buf,
+ size_t count)
+{
+ struct led_classdev *cdev = dev_get_drvdata(dev);
+ struct RGB *led = container_of(cdev, struct RGB, cdev);
+ struct MP3326_Led *chip = led->chip;
+ ssize_t ret;
+ int r_val, g_val, b_val;
+
+ ret = sscanf(buf, "%d %d %d", &r_val, &g_val, &b_val);
+ if (ret != 3) {
+ ret = sscanf(buf, "H %x %x %x", &r_val, &g_val, &b_val);
+ if (ret != 3)
+ return ret;
+ }
+ r_val = r_val * 4095 / 255 + ((r_val * 4095) % 255 > 127 ? 1 : 0);
+ g_val = g_val * 4095 / 255 + ((g_val * 4095) % 255 > 127 ? 1 : 0);
+ b_val = b_val * 4095 / 255 + ((b_val * 4095) % 255 > 127 ? 1 : 0);
+
+ ret = regmap_field_write(chip->channel_fields[led->red]->regmap_fields[COLOR_H4], r_val & 0x0f);
+ ret = regmap_field_write(chip->channel_fields[led->red]->regmap_fields[COLOR_L8], r_val >> 4);
+ ret = regmap_field_write(chip->channel_fields[led->green]->regmap_fields[COLOR_H4], g_val & 0x0f);
+ ret = regmap_field_write(chip->channel_fields[led->green]->regmap_fields[COLOR_L8], g_val >> 4);
+ ret = regmap_field_write(chip->channel_fields[led->blue]->regmap_fields[COLOR_H4], b_val & 0x0f);
+ ret = regmap_field_write(chip->channel_fields[led->blue]->regmap_fields[COLOR_L8], b_val >> 4);
+
+ if (ret)
+ return -EINVAL;
+ return count;
+}
+
+/*
+ *Show an integer which indicates current brightness.
+ */
+static ssize_t analog_Dim_show(struct device *dev,
+ struct device_attribute *attr,
+ char *buf)
+{
+ struct led_classdev *cdev = dev_get_drvdata(dev);
+ struct RGB *led = container_of(cdev, struct RGB, cdev);
+ struct MP3326_Led *chip = led->chip;
+ unsigned int val;
+
+ regmap_field_read(chip->channel_fields[led->red]->regmap_fields[BRIGHTNESS], &val);
+
+ return sysfs_emit(buf, "%u\n", val);
+}
+/*
+ * Input an integer value which changes the brightness.
+ * The integer is range from 0 to 63
+ */
+static ssize_t analog_Dim_store(struct device *dev,
+ struct device_attribute *attr,
+ const char *buf,
+ size_t count)
+{
+ struct led_classdev *cdev = dev_get_drvdata(dev);
+ struct RGB *led = container_of(cdev, struct RGB, cdev);
+ struct MP3326_Led *chip = led->chip;
+ ssize_t ret;
+ long long_val;
+
+ ret = kstrtol(buf, 10, &long_val);
+ if (long_val < 0)
+ return -EINVAL;
+ if (long_val > cdev->max_brightness) {
+ led->AnalogDim = cdev->max_brightness;
+ cdev->brightness = cdev->max_brightness;
+ } else {
+ led->AnalogDim = long_val;
+ cdev->brightness = long_val;
+ }
+
+ ret = regmap_field_write(chip->channel_fields[led->red]->regmap_fields[BRIGHTNESS], led->AnalogDim);
+ if (ret)
+ return -EINVAL;
+
+ ret = regmap_field_write(chip->channel_fields[led->green]->regmap_fields[BRIGHTNESS], led->AnalogDim);
+ if (ret)
+ return -EINVAL;
+
+ ret = regmap_field_write(chip->channel_fields[led->blue]->regmap_fields[BRIGHTNESS], led->AnalogDim);
+ if (ret)
+ return -EINVAL;
+ return count;
+}
+
+/*
+ * Show a string which indicates enable all channels of rgb.
+ * True indicates all channels of rgb enable.
+ * False indicates all channels of rgb disable.
+ */
+static ssize_t rgb_enable_show(struct device *dev,
+ struct device_attribute *attr,
+ char *buf)
+{
+ struct led_classdev *cdev = dev_get_drvdata(dev);
+ struct RGB *led = container_of(cdev, struct RGB, cdev);
+ struct MP3326_Led *chip = led->chip;
+ unsigned int rval = 0, val;
+
+ regmap_field_read(chip->channel_fields[led->red]->regmap_fields[ENABLE], &val);
+ rval |= val;
+ regmap_field_read(chip->channel_fields[led->green]->regmap_fields[ENABLE], &val);
+ rval |= val;
+ regmap_field_read(chip->channel_fields[led->blue]->regmap_fields[ENABLE], &val);
+ rval |= val;
+ regmap_read(chip->regmap, 0x05, &val);
+ if (rval)
+ return sysfs_emit(buf, "%s\n", "True");
+
+ return sysfs_emit(buf, "%s\n", "False");
+}
+
+/*
+ * Input 1 or 0 to control the channels of rgb enable/disable.
+ * If input 1, the channels of rgb is enable
+ * If input 0, the channels of rgb is disable
+ */
+static ssize_t rgb_enable_store(struct device *dev,
+ struct device_attribute *attr,
+ const char *buf,
+ size_t count)
+{
+ struct led_classdev *cdev = dev_get_drvdata(dev);
+ struct RGB *led = container_of(cdev, struct RGB, cdev);
+ struct MP3326_Led *chip = led->chip;
+ ssize_t ret;
+ long long_val;
+
+ ret = kstrtol(buf, 10, &long_val);
+ if (long_val) {
+ ret = regmap_field_write(chip->channel_fields[led->red]->regmap_fields[BRIGHTNESS], led->AnalogDim);
+ if (ret)
+ return -EINVAL;
+
+ ret = regmap_field_write(chip->channel_fields[led->green]->regmap_fields[BRIGHTNESS], led->AnalogDim);
+ if (ret)
+ return -EINVAL;
+
+ ret = regmap_field_write(chip->channel_fields[led->blue]->regmap_fields[BRIGHTNESS], led->AnalogDim);
+ if (ret)
+ return -EINVAL;
+
+
+ ret = regmap_field_write(chip->channel_fields[led->red]->regmap_fields[ENABLE], long_val);
+ if (ret)
+ return -EINVAL;
+
+ ret = regmap_field_write(chip->channel_fields[led->green]->regmap_fields[ENABLE], long_val);
+ if (ret)
+ return -EINVAL;
+
+ ret = regmap_field_write(chip->channel_fields[led->blue]->regmap_fields[ENABLE], long_val);
+ if (ret)
+ return -EINVAL;
+
+ led->IsAlive = true;
+ } else {
+ ret = regmap_field_write(chip->channel_fields[led->red]->regmap_fields[ENABLE], long_val);
+ if (ret)
+ return -EINVAL;
+
+
+ ret = regmap_field_write(chip->channel_fields[led->green]->regmap_fields[ENABLE], long_val);
+ if (ret)
+ return -EINVAL;
+
+ ret = regmap_field_write(chip->channel_fields[led->blue]->regmap_fields[ENABLE], long_val);
+ if (ret)
+ return -EINVAL;
+
+ led->IsAlive = false;
+ }
+
+ return count;
+}
+
+/*
+ * Show a string which indicates short fault of rgb.
+ * True indicates the rgb has a short fault.
+ * False indicates the rgb is normal.
+ */
+static ssize_t short_fault_show(struct device *dev,
+ struct device_attribute *attr,
+ char *buf)
+{
+ struct led_classdev *cdev = dev_get_drvdata(dev);
+ struct RGB *led = container_of(cdev, struct RGB, cdev);
+ struct MP3326_Led *chip = led->chip;
+ unsigned int rval = 0, val;
+
+ regmap_field_read(chip->channel_fields[led->red]->regmap_fields[SHORT_FAULT], &val);
+ rval |= val;
+ regmap_field_read(chip->channel_fields[led->green]->regmap_fields[SHORT_FAULT], &val);
+ rval |= val;
+ regmap_field_read(chip->channel_fields[led->blue]->regmap_fields[SHORT_FAULT], &val);
+ rval |= val;
+ if (rval)
+ return sysfs_emit(buf, "%s\n", "True");
+
+ return sysfs_emit(buf, "%s\n", "False");
+}
+
+/*
+ * Show a string which indicates open load fault of rgb.
+ * True indicates the rgb has an open load fault.
+ * False indicates the rgb is normal.
+ */
+static ssize_t open_fault_show(struct device *dev,
+ struct device_attribute *attr,
+ char *buf)
+{
+ struct led_classdev *cdev = dev_get_drvdata(dev);
+ struct RGB *led = container_of(cdev, struct RGB, cdev);
+ struct MP3326_Led *chip = led->chip;
+ unsigned int rval = 0, val;
+
+ regmap_field_read(chip->channel_fields[led->red]->regmap_fields[OPEN_FAULT], &val);
+ rval |= val;
+ regmap_field_read(chip->channel_fields[led->green]->regmap_fields[OPEN_FAULT], &val);
+ rval |= val;
+ regmap_field_read(chip->channel_fields[led->blue]->regmap_fields[OPEN_FAULT], &val);
+ rval |= val;
+
+ if (rval)
+ return sysfs_emit(buf, "%s\n", "True");
+
+ return sysfs_emit(buf, "%s\n", "False");
+}
+
+
+static DEVICE_ATTR_RW(rgb_enable);
+static DEVICE_ATTR_RW(pwm_Dim);
+static DEVICE_ATTR_RW(analog_Dim);
+static DEVICE_ATTR_RO(open_fault);
+static DEVICE_ATTR_RO(short_fault);
+static struct attribute *MP3266_led_sysfs_attrs[] = {
+ &dev_attr_rgb_enable.attr,
+ &dev_attr_pwm_Dim.attr,
+ &dev_attr_analog_Dim.attr,
+ &dev_attr_open_fault.attr,
+ &dev_attr_short_fault.attr,
+ NULL,
+};
+ATTRIBUTE_GROUPS(MP3266_led_sysfs);
+
+static int MP3266_probe_fw(struct device *dev, struct MP3326_Led *chip)
+{
+ struct fwnode_handle *child;
+ int ret;
+ int i;
+
+ device_for_each_child_node(dev, child) {
+ struct RGB *rgb;
+ struct led_init_data init_data = {};
+ u32 source, Red = 0, Green, Blue;
+
+ ret = fwnode_property_read_u32(child, "rgb_r", &Red);
+ if (ret) {
+ dev_err(dev, "Missing rgb_r property\n");
+ chip->num_rgbs--;
+ continue;
+ }
+
+ if (Red <= 0 || Red > Max_Channel) {
+ dev_err(dev, "LED reg %u out of range (max %u)(min 1)\n",
+ Red, Max_Channel);
+ chip->num_rgbs--;
+ continue;
+ }
+
+ ret = fwnode_property_read_u32(child, "rgb_g", &Green);
+
+ if (ret) {
+ dev_err(dev, "Missing rgb_g property\n");
+ chip->num_rgbs--;
+ continue;
+ }
+
+ if (Green <= 0 || Green > Max_Channel) {
+ dev_err(dev, "LED reg %u out of range (max %u)(min 1)\n",
+ Green, Max_Channel);
+ chip->num_rgbs--;
+ continue;
+ }
+
+ ret = fwnode_property_read_u32(child, "rgb_b", &Blue);
+
+ if (ret) {
+ dev_err(dev, "Missing rgb_b property\n");
+ chip->num_rgbs--;
+ continue;
+ }
+
+ if (Blue <= 0 || Blue > Max_Channel) {
+ dev_err(dev, "LED reg %u out of range (max %u)(min 1)\n",
+ Blue, Max_Channel);
+ chip->num_rgbs--;
+ continue;
+ }
+
+ ret = fwnode_property_read_u32(child, "brightness", &source);
+
+ if (ret) {
+ dev_err(dev, "Missing brightness property\n");
+ chip->num_rgbs--;
+ continue;
+ }
+
+ if (source < 0) {
+ dev_err(dev, "Brightness: %u must be greater than 0\n",
+ source);
+ chip->num_rgbs--;
+ continue;
+ } else if (source > 63) {
+ source = 63;
+ }
+ rgb = &chip->rgbs[i];
+ rgb->chip = chip;
+ rgb->red = Red-1;
+ rgb->green = Green-1;
+ rgb->blue = Blue-1;
+ rgb->AnalogDim = source;
+
+ rgb->cdev.groups = MP3266_led_sysfs_groups;
+ rgb->cdev.brightness_set_blocking = MP3326_led_set;
+ rgb->cdev.brightness = source;
+ rgb->cdev.max_brightness = 63;
+ init_data.fwnode = child;
+
+ ret = devm_led_classdev_register_ext(dev, &rgb->cdev,
+ &init_data);
+ if (ret) {
+ fwnode_handle_put(child);
+ break;
+ }
+ i++;
+ ret = regmap_field_write(chip->channel_fields[rgb->red]->regmap_fields[BRIGHTNESS], source);
+ ret = regmap_field_write(chip->channel_fields[rgb->green]->regmap_fields[BRIGHTNESS], source);
+ ret = regmap_field_write(chip->channel_fields[rgb->blue]->regmap_fields[BRIGHTNESS], source);
+ }
+ if (!chip->num_rgbs)
+ return -EINVAL;
+ return 0;
+}
+
+static int MP3326_leds_probe(struct i2c_client *client, const struct i2c_device_id *id)
+{
+ struct MP3326_Led *chip;
+ struct ChannelField *channel;
+ struct device_node *np = client->dev.of_node;
+ int ret, count, source;
+ int i, j;
+
+ count = device_get_child_node_count(&client->dev);
+ if (!count || count > 5) {
+ return dev_err_probe(&client->dev, -EINVAL,
+ "Incorrect number of rgbs (%d)", count);
+ }
+
+ chip = devm_kzalloc(&client->dev, struct_size(chip, rgbs, count), GFP_KERNEL);
+ if (!chip)
+ return -ENOMEM;
+ channel = devm_kzalloc(&client->dev, sizeof(struct ChannelField) * Max_Channel, GFP_KERNEL);
+ if (!channel)
+ return -ENOMEM;
+
+ chip->client = client;
+ chip->num_rgbs = count;
+
+ i2c_set_clientdata(client, chip);
+ chip->regmap = devm_regmap_init_i2c(client, &MP3326_regmap_config);
+ if (IS_ERR(chip->regmap))
+ return PTR_ERR(chip->regmap);
+
+
+ for (i = 0; i < Max_Channel; i++) {
+ for (j = 0; j < Max_CTRL; j++) {
+ channel[i].regmap_fields[j] = devm_regmap_field_alloc(&client->dev,
+ chip->regmap, MP3266_reg_fields[i].regmap_fields[j]);
+
+ if (IS_ERR(channel[i].regmap_fields[j])) {
+ dev_err(&client->dev, "regmap field alloc fail %d\n", i);
+ return PTR_ERR(channel[i].regmap_fields[j]);
+ }
+ }
+ chip->channel_fields[i] = &channel[i];
+ channel++;
+ }
+
+ if (IS_ERR(chip->channel_fields))
+ return PTR_ERR(chip->channel_fields);
+
+ ret = of_property_read_u32(np, "led-protect", &source);
+ if (!ret)
+ ret = regmap_update_bits(chip->regmap, 0x01, BIT(4) | BIT(5), source<<4);
+
+ ret = of_property_read_u32(np, "switch_status", &source);
+ if (ret)
+ regmap_update_bits(chip->regmap, 0x01, BIT(0), 0);
+ else
+ regmap_update_bits(chip->regmap, 0x01, BIT(0), source);
+
+ /*close all channel*/
+ regmap_write(chip->regmap, MP3326_PWM_CTRL_CHANNEL_9_16, 0);
+ regmap_write(chip->regmap, MP3326_PWM_CTRL_CHANNEL_1_8, 0);
+ mutex_init(&chip->mutex);
+ mutex_lock(&chip->mutex);
+
+ ret = MP3266_probe_fw(&client->dev, chip);
+ if (ret)
+ goto out_unlock;
+
+out_unlock:
+ mutex_unlock(&chip->mutex);
+ return ret;
+}
+
+static int MP3326_leds_remove(struct i2c_client *client)
+{
+ return 0;
+}
+
+static const struct i2c_device_id MP3326_led_id[] = {
+ {"MP3326", 0},
+ {}
+};
+MODULE_DEVICE_TABLE(i2c, MP3326_led_id);
+
+static const struct of_device_id MP3326_led_of_match[] = {
+ { .compatible = "mps,MP3326" },
+ {}
+};
+MODULE_DEVICE_TABLE(of, MP3326_led_of_match);
+
+static struct i2c_driver MP3326_leds_driver = {
+ .probe = MP3326_leds_probe,
+ .remove = MP3326_leds_remove,
+ .driver = {
+ .owner = THIS_MODULE,
+ .name = "MP3326_led",
+ .of_match_table = MP3326_led_of_match,
+ },
+
+ .id_table = MP3326_led_id,
+};
+
+module_i2c_driver(MP3326_leds_driver);
+
+MODULE_AUTHOR("Yuxi Wang <Yuxi.Wang@monolithicpower.com>");
+MODULE_DESCRIPTION("MP3326 Led driver");
+MODULE_LICENSE("GPL");
This patch adds a led driver for the mp3326 which is from Monolithic Power Systems, Inc. Signed-off-by: Yuxi Wang <Yuxi.Wang@monolithicpower.com> --- drivers/leds/Kconfig | 7 + drivers/leds/Makefile | 1 + drivers/leds/leds-mp3326.c | 836 +++++++++++++++++++++++++++++++++++++ 3 files changed, 844 insertions(+) create mode 100644 drivers/leds/leds-mp3326.c