From: ChiYuan Huang <[email protected]>
Add mt6360 isnk channel hardware breath mode support.
Signed-off-by: ChiYuan Huang <[email protected]>
---
drivers/leds/flash/Kconfig | 1 +
drivers/leds/flash/leds-mt6360.c | 207 ++++++++++++++++++++++++++++++++++++++-
2 files changed, 207 insertions(+), 1 deletion(-)
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",
--
2.7.4
Matti Vaittinen <[email protected]> 於 2022年4月28日 週四 下午7:51寫道:
>
> Hi ChiYuan!
>
> On Thu, Apr 28, 2022 at 1:03 PM cy_huang <[email protected]> wrote:
> > From: ChiYuan Huang <[email protected]>
> >
> > Add mt6360 isnk channel hardware breath mode support.
> >
> > Signed-off-by: ChiYuan Huang <[email protected]>
> >
> > +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));
Hi ChiYuan!
On Thu, Apr 28, 2022 at 1:03 PM cy_huang <[email protected]> wrote:
> From: ChiYuan Huang <[email protected]>
>
> Add mt6360 isnk channel hardware breath mode support.
>
> Signed-off-by: ChiYuan Huang <[email protected]>
>
> +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
--
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));