diff mbox series

[4/4] leds: flash: mt6360: Add mt6360 isnk channel hardwre breath mode support

Message ID 1651138365-17362-5-git-send-email-u0084500@gmail.com (mailing list archive)
State New, archived
Headers show
Series leds: flash: mt6360: Apply the fixes and hardware features | expand

Commit Message

ChiYuan Huang April 28, 2022, 9:32 a.m. UTC
From: ChiYuan Huang <cy_huang@richtek.com>

Add mt6360 isnk channel hardware breath mode support.

Signed-off-by: ChiYuan Huang <cy_huang@richtek.com>
---
 drivers/leds/flash/Kconfig       |   1 +
 drivers/leds/flash/leds-mt6360.c | 207 ++++++++++++++++++++++++++++++++++++++-
 2 files changed, 207 insertions(+), 1 deletion(-)

Comments

Matti Vaittinen April 28, 2022, 11:50 a.m. UTC | #1
Hi ChiYuan!

On Thu, Apr 28, 2022 at 1:03 PM cy_huang <u0084500@gmail.com> wrote:
> From: ChiYuan Huang <cy_huang@richtek.com>
>
> Add mt6360 isnk channel hardware breath mode support.
>
> Signed-off-by: ChiYuan Huang <cy_huang@richtek.com>
>
> +static int mt6360_gen_breath_reg_config(struct led_pattern *pattern, u32 len,
> +                                       u8 *vals, int val_len)
> +{
> +       static const struct linear_range tranges[MT6360_ILED_RANGE_MAX] = {
> +               { 125, 0, 15, 250 }, /* tr/f12 and ton, unit: millisecond */
> +               { 250, 0, 15, 500 }, /* toff, unit: millisecond */
> +       };

It's nice to see you are using the linear_ranges helpers here! Just a
minor remark - do you think you could use field names in linear_ranges
initializations? That would make it less likely the driver breaks if
someone changes the struct linear_range definition. Eg, use something
like:

static const struct linear_range tranges[MT6360_ILED_RANGE_MAX] = {
        /* tr/f12 and ton, unit: millisecond */
        { .min = 125, .min_sel = 0, .max_sel = 15, .step = 250 },
        /* toff, unit: millisecond */
        { .min = 250, .min_sel = 0, .max_sel = 15, .step = 500 },
};

Do you think that would work?

Best Regards
-- Matti
ChiYuan Huang April 28, 2022, 2:57 p.m. UTC | #2
Matti Vaittinen <mazziesaccount@gmail.com> 於 2022年4月28日 週四 下午7:51寫道:
>
> Hi ChiYuan!
>
> On Thu, Apr 28, 2022 at 1:03 PM cy_huang <u0084500@gmail.com> wrote:
> > From: ChiYuan Huang <cy_huang@richtek.com>
> >
> > Add mt6360 isnk channel hardware breath mode support.
> >
> > Signed-off-by: ChiYuan Huang <cy_huang@richtek.com>
> >
> > +static int mt6360_gen_breath_reg_config(struct led_pattern *pattern, u32 len,
> > +                                       u8 *vals, int val_len)
> > +{
> > +       static const struct linear_range tranges[MT6360_ILED_RANGE_MAX] = {
> > +               { 125, 0, 15, 250 }, /* tr/f12 and ton, unit: millisecond */
> > +               { 250, 0, 15, 500 }, /* toff, unit: millisecond */
> > +       };
>
> It's nice to see you are using the linear_ranges helpers here! Just a
> minor remark - do you think you could use field names in linear_ranges
> initializations? That would make it less likely the driver breaks if
> someone changes the struct linear_range definition. Eg, use something
> like:
>
> static const struct linear_range tranges[MT6360_ILED_RANGE_MAX] = {
>         /* tr/f12 and ton, unit: millisecond */
>         { .min = 125, .min_sel = 0, .max_sel = 15, .step = 250 },
>         /* toff, unit: millisecond */
>         { .min = 250, .min_sel = 0, .max_sel = 15, .step = 500 },
> };
>
> Do you think that would work?
Sure, it works.
To specify the field name can be compatible if the struct changes in the future.
Thanks.
>
> Best Regards
> -- Matti
>
> --
>
> Matti Vaittinen
> Linux kernel developer at ROHM Semiconductors
> Oulu Finland
>
> ~~ When things go utterly wrong vim users can always type :help! ~~
>
> Discuss - Estimate - Plan - Report and finally accomplish this:
> void do_work(int time) __attribute__ ((const));
diff mbox series

Patch

diff --git a/drivers/leds/flash/Kconfig b/drivers/leds/flash/Kconfig
index 38b325c..039ff50 100644
--- a/drivers/leds/flash/Kconfig
+++ b/drivers/leds/flash/Kconfig
@@ -54,6 +54,7 @@  config LEDS_MT6360
 	depends on LEDS_CLASS_MULTICOLOR || !LEDS_CLASS_MULTICOLOR
 	depends on V4L2_FLASH_LED_CLASS || !V4L2_FLASH_LED_CLASS
 	depends on MFD_MT6360
+	select LINEAR_RANGE
 	help
 	  This option enables support for dual Flash LED drivers found on
 	  Mediatek MT6360 PMIC.
diff --git a/drivers/leds/flash/leds-mt6360.c b/drivers/leds/flash/leds-mt6360.c
index 8fe3dc4..b4a702c 100644
--- a/drivers/leds/flash/leds-mt6360.c
+++ b/drivers/leds/flash/leds-mt6360.c
@@ -7,6 +7,7 @@ 
 #include <linux/kernel.h>
 #include <linux/led-class-flash.h>
 #include <linux/led-class-multicolor.h>
+#include <linux/linear_range.h>
 #include <linux/module.h>
 #include <linux/mutex.h>
 #include <linux/platform_device.h>
@@ -24,7 +25,14 @@  enum {
 	MT6360_MAX_LEDS
 };
 
+enum mt6360_iled_range {
+	MT6360_ILED_RANGE_TRF12ON = 0, /* tr1, tr2, ton, tf1, tf2 */
+	MT6360_ILED_RANGE_TOFF,
+	MT6360_ILED_RANGE_MAX
+};
+
 #define MT6360_ISNK_PWM_MODE		0
+#define MT6360_ISNK_BREATH_MODE		1
 #define MT6360_ISNK_CC_MODE		2
 
 #define MT6360_REG_RGBEN		0x380
@@ -32,6 +40,7 @@  enum {
 #define MT6360_REG_DIM(_led_no)		(0x385 + (_led_no))
 #define MT6360_REG_FREQ1		0x389
 #define MT6360_REG_FREQ2		0x38A
+#define MT6360_REG_ISNK_BREATH(_ledno)	(0x38B + ((_ledno) * 3))
 #define MT6360_ISNK_ENMASK(_led_no)	BIT(7 - (_led_no))
 #define MT6360_FREQ13_MASK		GENMASK(7, 5)
 #define MT6360_FREQ2_MASK		GENMASK(4, 2)
@@ -39,6 +48,14 @@  enum {
 #define MT6360_ISNK_MODE_SHFT		6
 #define MT6360_ISNK_MASK		GENMASK(4, 0)
 #define MT6360_CHRINDSEL_MASK		BIT(3)
+#define MT6360_ISNK_TR1_MASK		GENMASK(7, 4)
+#define MT6360_ISNK_TR2_MASK		GENMASK(3, 0)
+#define MT6360_ISNK_TF1_MASK		GENMASK(7, 4)
+#define MT6360_ISNK_TF2_MASK		GENMASK(3, 0)
+#define MT6360_ISNK_TON_MASK		GENMASK(7, 4)
+#define MT6360_ISNK_TOFF_MASK		GENMASK(3, 0)
+#define MT6360_BREATH_PATTERN_CNT	6
+#define MT6360_BREATH_CFGRG_CNT		3
 
 /* Virtual definition for multicolor */
 #define MT6360_VIRTUAL_MULTICOLOR	(MT6360_MAX_LEDS + 1)
@@ -108,6 +125,46 @@  struct mt6360_priv {
 	struct mt6360_led leds[];
 };
 
+static int mt6360_gen_breath_reg_config(struct led_pattern *pattern, u32 len,
+					u8 *vals, int val_len)
+{
+	static const struct linear_range tranges[MT6360_ILED_RANGE_MAX] = {
+		{ 125, 0, 15, 250 }, /* tr/f12 and ton, unit: millisecond */
+		{ 250, 0, 15, 500 }, /* toff, unit: millisecond */
+	};
+	static const struct {
+		int rg_offset;
+		int mask;
+		enum mt6360_iled_range rid;
+	} params[MT6360_BREATH_PATTERN_CNT] = {
+		{ 0, MT6360_ISNK_TR1_MASK, MT6360_ILED_RANGE_TRF12ON },
+		{ 0, MT6360_ISNK_TR2_MASK, MT6360_ILED_RANGE_TRF12ON },
+		{ 2, MT6360_ISNK_TON_MASK, MT6360_ILED_RANGE_TRF12ON },
+		{ 1, MT6360_ISNK_TF1_MASK, MT6360_ILED_RANGE_TRF12ON },
+		{ 1, MT6360_ISNK_TF2_MASK, MT6360_ILED_RANGE_TRF12ON },
+		{ 2, MT6360_ISNK_TOFF_MASK, MT6360_ILED_RANGE_TOFF }
+	};
+	unsigned int sel;
+	int i, shift;
+
+	/* Must contain 6 tuples to configure tr1, tr2, ton, tf1, tf2, toff */
+	if (len != MT6360_BREATH_PATTERN_CNT ||
+	    val_len != MT6360_BREATH_CFGRG_CNT)
+		return -EINVAL;
+
+	for (i = 0; i < ARRAY_SIZE(params); i++) {
+		linear_range_get_selector_within(tranges + params[i].rid,
+						 pattern[i].delta_t, &sel);
+
+		shift = ffs(params[i].mask) - 1;
+
+		vals[params[i].rg_offset] &= ~params[i].mask;
+		vals[params[i].rg_offset] |= sel << shift;
+	}
+
+	return 0;
+}
+
 static int mt6360_get_selected_freq_duty(unsigned long on, unsigned long off,
 					 unsigned int *fsel, unsigned int *dsel)
 {
@@ -165,6 +222,95 @@  static int mt6360_set_pwm_dimming_param(struct mt6360_priv *priv,
 	return regmap_write(priv->regmap, MT6360_REG_DIM(led_no), dsel);
 }
 
+static int mt6360_mc_pattern_set(struct led_classdev *lcdev,
+				 struct led_pattern *pattern, u32 len,
+				 int repeat)
+{
+	struct led_classdev_mc *mccdev = lcdev_to_mccdev(lcdev);
+	struct mt6360_led *led = container_of(mccdev, struct mt6360_led, mc);
+	struct mt6360_priv *priv = led->priv;
+	unsigned int reg_base;
+	u8 cfg_vals[MT6360_BREATH_CFGRG_CNT] = {0};
+	u8 mc_reg[3];
+	int i, ret;
+
+	ret = mt6360_gen_breath_reg_config(pattern, len, cfg_vals,
+					   sizeof(cfg_vals));
+	if (ret)
+		return ret;
+
+	mutex_lock(&priv->lock);
+
+	ret = regmap_raw_read(priv->regmap, MT6360_REG_ISNK(0), mc_reg, 3);
+	if (ret)
+		goto out;
+
+	/* Configure all subleds to CC mode first */
+	for (i = 0; i < mccdev->num_colors; i++) {
+		struct mc_subled *subled = mccdev->subled_info + i;
+		int ch_idx = subled->channel;
+
+		mc_reg[ch_idx] &= ~MT6360_ISNK_MODE_MASK;
+		mc_reg[ch_idx] |= MT6360_ISNK_CC_MODE << MT6360_ISNK_MODE_SHFT;
+	}
+
+	ret = regmap_raw_write(priv->regmap, MT6360_REG_ISNK(0), mc_reg, 3);
+	if (ret)
+		goto out;
+
+	for (i = 0; i < mccdev->num_colors; i++) {
+		struct mc_subled *subled = mccdev->subled_info + i;
+		int ch_idx = subled->channel;
+
+		reg_base = MT6360_REG_ISNK_BREATH(ch_idx);
+
+		ret = regmap_raw_write(priv->regmap, reg_base, cfg_vals,
+				       sizeof(cfg_vals));
+		if (ret)
+			goto out;
+
+		mc_reg[ch_idx] &= ~MT6360_ISNK_MODE_MASK;
+		mc_reg[ch_idx] |=
+			MT6360_ISNK_BREATH_MODE << MT6360_ISNK_MODE_SHFT;
+	}
+
+	ret = regmap_raw_write(priv->regmap, MT6360_REG_ISNK(0), mc_reg, 3);
+
+out:
+	mutex_unlock(&priv->lock);
+	return ret;
+}
+
+static int mt6360_mc_pattern_clear(struct led_classdev *lcdev)
+{
+	struct led_classdev_mc *mccdev = lcdev_to_mccdev(lcdev);
+	struct mt6360_led *led = container_of(mccdev, struct mt6360_led, mc);
+	struct mt6360_priv *priv = led->priv;
+	u8 mc_reg[3];
+	int i, ret;
+
+	mutex_lock(&priv->lock);
+
+	ret = regmap_raw_read(priv->regmap, MT6360_REG_ISNK(0), mc_reg, 3);
+	if (ret)
+		goto out;
+
+	/* Configure all subleds to CC mode first */
+	for (i = 0; i < mccdev->num_colors; i++) {
+		struct mc_subled *subled = mccdev->subled_info + i;
+		int ch_idx = subled->channel;
+
+		mc_reg[ch_idx] &= ~MT6360_ISNK_MODE_MASK;
+		mc_reg[ch_idx] |= MT6360_ISNK_CC_MODE << MT6360_ISNK_MODE_SHFT;
+	}
+
+	ret = regmap_raw_write(priv->regmap, MT6360_REG_ISNK(0), mc_reg, 3);
+
+out:
+	mutex_unlock(&priv->lock);
+	return ret;
+}
+
 static int mt6360_mc_blink_set(struct led_classdev *lcdev, unsigned long *don,
 			       unsigned long *doff)
 {
@@ -273,6 +419,60 @@  static int mt6360_mc_brightness_set(struct led_classdev *lcdev,
 	return ret;
 }
 
+static int mt6360_isnk_pattern_set(struct led_classdev *lcdev,
+				   struct led_pattern *pattern, u32 len,
+				   int repeat)
+{
+	struct mt6360_led *led = container_of(lcdev, struct mt6360_led, isnk);
+	struct mt6360_priv *priv = led->priv;
+	unsigned int reg_base = MT6360_REG_ISNK_BREATH(led->led_no);
+	u8 cfg_vals[MT6360_BREATH_CFGRG_CNT] = {0};
+	int ret;
+
+	ret = mt6360_gen_breath_reg_config(pattern, len, cfg_vals,
+					   sizeof(cfg_vals));
+	if (ret)
+		return ret;
+
+	mutex_lock(&priv->lock);
+
+	ret = regmap_raw_write(priv->regmap, reg_base, cfg_vals,
+			       sizeof(cfg_vals));
+	if (ret)
+		goto out;
+
+	/* Toggle mode change to make new parameter applied */
+	ret = regmap_update_bits(priv->regmap, MT6360_REG_ISNK(led->led_no),
+				 MT6360_ISNK_MODE_MASK, MT6360_ISNK_CC_MODE
+				 << MT6360_ISNK_MODE_SHFT);
+	if (ret)
+		goto out;
+
+	ret = regmap_update_bits(priv->regmap, MT6360_REG_ISNK(led->led_no),
+				 MT6360_ISNK_MODE_MASK, MT6360_ISNK_BREATH_MODE
+				 << MT6360_ISNK_MODE_SHFT);
+
+out:
+	mutex_unlock(&priv->lock);
+	return 0;
+}
+
+static int mt6360_isnk_pattern_clear(struct led_classdev *lcdev)
+{
+	struct mt6360_led *led = container_of(lcdev, struct mt6360_led, isnk);
+	struct mt6360_priv *priv = led->priv;
+	int ret;
+
+	mutex_lock(&priv->lock);
+
+	ret = regmap_update_bits(priv->regmap, MT6360_REG_ISNK(led->led_no),
+				 MT6360_ISNK_MODE_MASK, MT6360_ISNK_CC_MODE
+				 << MT6360_ISNK_MODE_SHFT);
+
+	mutex_unlock(&priv->lock);
+	return ret;
+}
+
 static int mt6360_isnk_blink_set(struct led_classdev *lcdev, unsigned long *don,
 				 unsigned long *doff)
 {
@@ -859,6 +1059,8 @@  static int mt6360_init_isnk_properties(struct mt6360_led *led,
 		lcdev = &led->mc.led_cdev;
 		lcdev->brightness_set_blocking = mt6360_mc_brightness_set;
 		lcdev->blink_set = mt6360_mc_blink_set;
+		lcdev->pattern_set = mt6360_mc_pattern_set;
+		lcdev->pattern_clear = mt6360_mc_pattern_clear;
 	} else {
 		if (led->led_no == MT6360_LED_ISNKML) {
 			step_uA = MT6360_ISNKML_STEPUA;
@@ -869,8 +1071,11 @@  static int mt6360_init_isnk_properties(struct mt6360_led *led,
 		lcdev->brightness_set_blocking = mt6360_isnk_brightness_set;
 
 		/* Suppose only ISNK1/2/3 support mode change */
-		if (led->led_no != MT6360_LED_ISNKML)
+		if (led->led_no != MT6360_LED_ISNKML) {
 			lcdev->blink_set = mt6360_isnk_blink_set;
+			lcdev->pattern_set = mt6360_isnk_pattern_set;
+			lcdev->pattern_clear = mt6360_isnk_pattern_clear;
+		}
 	}
 
 	ret = fwnode_property_read_u32(init_data->fwnode, "led-max-microamp",