diff mbox series

[3/4] leds: flash: mt6360: Add mt6360 isnk channel hardware timer dimming mode support

Message ID 1651138365-17362-4-git-send-email-u0084500@gmail.com
State New
Headers show
Series leds: flash: mt6360: Apply the fixes and hardware features | expand

Commit Message

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

Add mt6360 isnk channel hardware timer dimming mode support.

Signed-off-by: ChiYuan Huang <cy_huang@richtek.com>
---
 drivers/leds/flash/leds-mt6360.c | 209 +++++++++++++++++++++++++++++++++++++--
 1 file changed, 203 insertions(+), 6 deletions(-)
diff mbox series

Patch

diff --git a/drivers/leds/flash/leds-mt6360.c b/drivers/leds/flash/leds-mt6360.c
index 712cd46..8fe3dc4 100644
--- a/drivers/leds/flash/leds-mt6360.c
+++ b/drivers/leds/flash/leds-mt6360.c
@@ -24,9 +24,19 @@  enum {
 	MT6360_MAX_LEDS
 };
 
+#define MT6360_ISNK_PWM_MODE		0
+#define MT6360_ISNK_CC_MODE		2
+
 #define MT6360_REG_RGBEN		0x380
 #define MT6360_REG_ISNK(_led_no)	(0x381 + (_led_no))
+#define MT6360_REG_DIM(_led_no)		(0x385 + (_led_no))
+#define MT6360_REG_FREQ1		0x389
+#define MT6360_REG_FREQ2		0x38A
 #define MT6360_ISNK_ENMASK(_led_no)	BIT(7 - (_led_no))
+#define MT6360_FREQ13_MASK		GENMASK(7, 5)
+#define MT6360_FREQ2_MASK		GENMASK(4, 2)
+#define MT6360_ISNK_MODE_MASK		GENMASK(7, 6)
+#define MT6360_ISNK_MODE_SHFT		6
 #define MT6360_ISNK_MASK		GENMASK(4, 0)
 #define MT6360_CHRINDSEL_MASK		BIT(3)
 
@@ -98,13 +108,128 @@  struct mt6360_priv {
 	struct mt6360_led leds[];
 };
 
+static int mt6360_get_selected_freq_duty(unsigned long on, unsigned long off,
+					 unsigned int *fsel, unsigned int *dsel)
+{
+	static const unsigned int freq_ms[] = { 8000, 4000, 2000, 1000,
+						500, 250, 8, 4 };
+	unsigned int sum = on + off;
+	int i;
+
+	/* Check if val is less than the smallest or larger then the largest */
+	if (sum > freq_ms[0] || sum < freq_ms[ARRAY_SIZE(freq_ms) - 1])
+		return -EINVAL;
+
+	/* Find the frequency for less or equal one */
+	for (i = 0; i < ARRAY_SIZE(freq_ms) - 1; i++) {
+		if (sum >= freq_ms[i])
+			break;
+	}
+
+	*fsel = i;
+	*dsel = on * 255 / sum;
+	return 0;
+}
+
+static int mt6360_set_pwm_dimming_param(struct mt6360_priv *priv,
+					unsigned int led_no, unsigned int fsel,
+					unsigned int dsel)
+{
+	unsigned int freq_reg, freq_mask, freq_shift;
+	int ret;
+
+	switch (led_no) {
+	case MT6360_LED_ISNK1:
+		freq_reg = MT6360_REG_FREQ1;
+		freq_mask = MT6360_FREQ13_MASK;
+		break;
+	case MT6360_LED_ISNK2:
+		freq_reg = MT6360_REG_FREQ1;
+		freq_mask = MT6360_FREQ2_MASK;
+		break;
+	case MT6360_LED_ISNK3:
+		freq_reg = MT6360_REG_FREQ2;
+		freq_mask = MT6360_FREQ13_MASK;
+		break;
+	default:
+		return -EINVAL;
+	}
+
+	freq_shift = ffs(freq_mask) - 1;
+
+	ret = regmap_update_bits(priv->regmap, freq_reg, freq_mask,
+				 fsel << freq_shift);
+	if (ret)
+		return ret;
+
+	return regmap_write(priv->regmap, MT6360_REG_DIM(led_no), dsel);
+}
+
+static int mt6360_mc_blink_set(struct led_classdev *lcdev, unsigned long *don,
+			       unsigned long *doff)
+{
+	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 freq_sel, duty_sel;
+	u8 mc_reg[3];
+	int i, ret;
+
+	if (!*don && !*doff)
+		*don = *doff = 500;
+
+	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;
+
+	/* Get the desired selector for frequency and duty */
+	ret = mt6360_get_selected_freq_duty(*don, *doff, &freq_sel, &duty_sel);
+	if (ret)
+		goto out;
+
+	/* Configure all subleds dim freq/duty and change to PWM mode */
+	for (i = 0; i < mccdev->num_colors; i++) {
+		struct mc_subled *subled = mccdev->subled_info + i;
+		int ch_idx = subled->channel;
+
+		ret = mt6360_set_pwm_dimming_param(priv, ch_idx, freq_sel,
+						   duty_sel);
+		if (ret)
+			goto out;
+
+		mc_reg[ch_idx] &= ~MT6360_ISNK_MODE_MASK;
+		mc_reg[ch_idx] |= MT6360_ISNK_PWM_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_brightness_set(struct led_classdev *lcdev,
 				    enum led_brightness level)
 {
 	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;
-	u32 real_bright, enable_mask = 0, enable = 0;
+	u32 real_bright, enable_mask = 0, enable = 0, val;
 	int i, ret;
 
 	mutex_lock(&priv->lock);
@@ -113,11 +238,25 @@  static int mt6360_mc_brightness_set(struct led_classdev *lcdev,
 
 	for (i = 0; i < mccdev->num_colors; i++) {
 		struct mc_subled *subled = mccdev->subled_info + i;
+		unsigned int bright_reg = MT6360_REG_ISNK(subled->channel);
 
 		real_bright = min(lcdev->max_brightness, subled->brightness);
-		ret = regmap_update_bits(priv->regmap,
-					 MT6360_REG_ISNK(subled->channel),
-					 MT6360_ISNK_MASK, real_bright);
+
+		ret = regmap_read(priv->regmap, bright_reg, &val);
+		if (ret)
+			goto out;
+
+		/* Assign the new brightness value */
+		val &= ~MT6360_ISNK_MASK;
+		val |= (real_bright & MT6360_ISNK_MASK);
+
+		/* If LED_OFF, always configure back to CC mode */
+		if (level == LED_OFF) {
+			val &= ~MT6360_ISNK_MODE_MASK;
+			val |= MT6360_ISNK_CC_MODE << MT6360_ISNK_MODE_SHFT;
+		}
+
+		ret = regmap_write(priv->regmap, bright_reg, val);
 		if (ret)
 			goto out;
 
@@ -134,6 +273,44 @@  static int mt6360_mc_brightness_set(struct led_classdev *lcdev,
 	return ret;
 }
 
+static int mt6360_isnk_blink_set(struct led_classdev *lcdev, unsigned long *don,
+				 unsigned long *doff)
+{
+	struct mt6360_led *led = container_of(lcdev, struct mt6360_led, isnk);
+	struct mt6360_priv *priv = led->priv;
+	unsigned int freq_sel, duty_sel;
+	int ret;
+
+	if (!*don && !*doff)
+		*don = *doff = 500;
+
+	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);
+	if (ret)
+		goto out;
+
+	/* Get the desired selector for frequency and duty */
+	ret = mt6360_get_selected_freq_duty(*don, *doff, &freq_sel, &duty_sel);
+	if (ret)
+		goto out;
+
+	ret = mt6360_set_pwm_dimming_param(priv, led->led_no, freq_sel,
+					   duty_sel);
+	if (ret)
+		goto out;
+
+	ret = regmap_update_bits(priv->regmap, MT6360_REG_ISNK(led->led_no),
+				 MT6360_ISNK_MODE_MASK,
+				 MT6360_ISNK_PWM_MODE << MT6360_ISNK_MODE_SHFT);
+
+out:
+	mutex_unlock(&priv->lock);
+	return ret;
+}
+
 static int mt6360_isnk_brightness_set(struct led_classdev *lcdev,
 				      enum led_brightness level)
 {
@@ -141,12 +318,27 @@  static int mt6360_isnk_brightness_set(struct led_classdev *lcdev,
 	struct mt6360_priv *priv = led->priv;
 	u32 enable_mask = MT6360_ISNK_ENMASK(led->led_no);
 	u32 val = level ? MT6360_ISNK_ENMASK(led->led_no) : 0;
+	unsigned int bright_val;
 	int ret;
 
 	mutex_lock(&priv->lock);
 
-	ret = regmap_update_bits(priv->regmap, MT6360_REG_ISNK(led->led_no),
-				 MT6360_ISNK_MASK, level);
+	ret = regmap_read(priv->regmap, MT6360_REG_ISNK(led->led_no),
+			  &bright_val);
+	if (ret)
+		goto out;
+
+	bright_val &= ~MT6360_ISNK_MASK;
+	bright_val |= (level & MT6360_ISNK_MASK);
+
+	/* If LED_OFF, always configure back to CC mode */
+	if (led->led_no != MT6360_LED_ISNKML && level == LED_OFF) {
+		bright_val &= ~MT6360_ISNK_MODE_MASK;
+		bright_val |= MT6360_ISNK_CC_MODE << MT6360_ISNK_MODE_SHFT;
+	}
+
+	ret = regmap_write(priv->regmap, MT6360_REG_ISNK(led->led_no),
+			   bright_val);
 	if (ret)
 		goto out;
 
@@ -666,6 +858,7 @@  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;
 	} else {
 		if (led->led_no == MT6360_LED_ISNKML) {
 			step_uA = MT6360_ISNKML_STEPUA;
@@ -674,6 +867,10 @@  static int mt6360_init_isnk_properties(struct mt6360_led *led,
 
 		lcdev = &led->isnk;
 		lcdev->brightness_set_blocking = mt6360_isnk_brightness_set;
+
+		/* Suppose only ISNK1/2/3 support mode change */
+		if (led->led_no != MT6360_LED_ISNKML)
+			lcdev->blink_set = mt6360_isnk_blink_set;
 	}
 
 	ret = fwnode_property_read_u32(init_data->fwnode, "led-max-microamp",