+ unsigned int fled_strobe_used;
+ unsigned int fled_torch_used;
+};
+
+static int mt6360_isnk_brightness_set(struct led_classdev *lcdev, enum
led_brightness level)
+{
+ struct mt6360_led *led = container_of(lcdev, struct mt6360_led, isnk);
+ 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;
+ int ret;
+
+ dev_dbg(lcdev->dev, "[%d] brightness %d\n", led->led_no, level);
+
+ if (level) {
+ ret = regmap_update_bits(priv->regmap,
MT6360_REG_ISNK(led->led_no),
+ MT6360_ISNK_MASK, level - 1);
+ if (ret)
+ return ret;
+ }
+
+ ret = regmap_update_bits(priv->regmap, MT6360_REG_RGBEN, enable_mask,
val);
+ if (ret)
+ return ret;
+
+ return 0;
+}
+
+static int mt6360_torch_brightness_set(struct led_classdev *lcdev, enum
led_brightness level)
+{
+ struct mt6360_led *led = container_of(lcdev, struct mt6360_led,
flash.led_cdev);
+ struct mt6360_priv *priv = led->priv;
+ u32 enable_mask = MT6360_TORCHEN_MASK | MT6360_FLCSEN_MASK(led->led_no);
+ u32 val = (level) ? MT6360_FLCSEN_MASK(led->led_no) : 0;
+ u32 prev = priv->fled_torch_used, curr;
+ int ret;
+
+ dev_dbg(lcdev->dev, "[%d] brightness %d\n", led->led_no, level);
+ if (priv->fled_strobe_used) {
+ dev_warn(lcdev->dev, "Please disable strobe first [%d]\n",
priv->fled_strobe_used);
+ return -EINVAL;
+ }
+
+ if (level)
+ curr = prev | BIT(led->led_no);
+ else
+ curr = prev & (~BIT(led->led_no));
+
+ if (curr)
+ val |= MT6360_TORCHEN_MASK;
+
+ if (level) {
+ ret = regmap_update_bits(priv->regmap,
MT6360_REG_FLEDITOR(led->led_no),
+ MT6360_ITORCH_MASK, level - 1);
+ if (ret)
+ return ret;
+ }
+
+ ret = regmap_update_bits(priv->regmap, MT6360_REG_FLEDEN, enable_mask,
val);
+ if (ret)
+ return ret;
+
+ priv->fled_torch_used = curr;
+
+ return 0;
+}
+
+static int mt6360_strobe_brightness_set(struct led_classdev_flash *fl_cdev,
u32 brightness)
+{
+ struct mt6360_led *led = container_of(fl_cdev, struct mt6360_led,
flash);
+ struct led_classdev *lcdev = &fl_cdev->led_cdev;
+
+ dev_dbg(lcdev->dev, "[%d] strobe brightness %d\n", led->led_no,
brightness);
+ return 0;
+}
+
+static int _mt6360_strobe_brightness_set(struct led_classdev_flash *fl_cdev,
u32 brightness)
+{
+ struct mt6360_led *led = container_of(fl_cdev, struct mt6360_led,
flash);
+ struct mt6360_priv *priv = led->priv;
+ struct led_flash_setting *s = &fl_cdev->brightness;
+ u32 val = (brightness - s->min) / s->step;
+
+ return regmap_update_bits(priv->regmap,
MT6360_REG_FLEDISTRB(led->led_no),
+ MT6360_ISTROBE_MASK, val);
+}
+
+static int mt6360_strobe_set(struct led_classdev_flash *fl_cdev, bool state)
+{
+ struct mt6360_led *led = container_of(fl_cdev, struct mt6360_led,
flash);
+ struct mt6360_priv *priv = led->priv;
+ struct led_classdev *lcdev = &fl_cdev->led_cdev;
+ struct led_flash_setting *s = &fl_cdev->brightness;
+ u32 enable_mask = MT6360_STROBEN_MASK | MT6360_FLCSEN_MASK(led->led_no);
+ u32 val = state ? MT6360_FLCSEN_MASK(led->led_no) : 0;
+ u32 prev = priv->fled_strobe_used, curr;
+ int ret;
+
+ dev_dbg(lcdev->dev, "[%d] strobe state %d\n", led->led_no, state);
+ if (priv->fled_torch_used) {
+ dev_warn(lcdev->dev, "Please disable torch first [0x%x]\n",
priv->fled_torch_used);
+ return -EINVAL;
+ }
+
+ if (state)
+ curr = prev | BIT(led->led_no);
+ else
+ curr = prev & (~BIT(led->led_no));
+
+ if (curr)
+ val |= MT6360_STROBEN_MASK;
+
+ ret = regmap_update_bits(priv->regmap, MT6360_REG_FLEDEN, enable_mask,
val);
+ if (ret) {
+ dev_err(lcdev->dev, "[%d] control current source %d fail\n",
led->led_no, state);
+ return ret;
+ }
+
+ /* used to prevent flash current spike when torch on */
+ ret = _mt6360_strobe_brightness_set(fl_cdev, state ? s->val : s->min);
+ if (ret)
+ return ret;
+
+ if (!prev && curr)
+ usleep_range(5000, 6000);
+ else if (prev && !curr)
+ udelay(500);
+
+ priv->fled_strobe_used = curr;
+ return 0;
+}
+
+static int mt6360_strobe_get(struct led_classdev_flash *fl_cdev, bool *state)
+{
+ struct mt6360_led *led = container_of(fl_cdev, struct mt6360_led,
flash);
+ struct mt6360_priv *priv = led->priv;
+
+ *state = !!(priv->fled_strobe_used & BIT(led->led_no));
+ return 0;
+}
+
+static int mt6360_timeout_set(struct led_classdev_flash *fl_cdev, u32 timeout)
+{
+ struct mt6360_led *led = container_of(fl_cdev, struct mt6360_led,
flash);
+ struct mt6360_priv *priv = led->priv;
+ struct led_flash_setting *s = &fl_cdev->timeout;
+ u32 val = (timeout - s->min) / s->step;
+
+ return regmap_update_bits(priv->regmap, MT6360_REG_STRBTO,
MT6360_STRBTO_MASK, val);
+
+}
+
+static int mt6360_fault_get(struct led_classdev_flash *fl_cdev, u32 *fault)
+{
+ struct mt6360_led *led = container_of(fl_cdev, struct mt6360_led,
flash);
+ struct mt6360_priv *priv = led->priv;
+ u16 fled_stat;
+ unsigned int chg_stat, strobe_timeout_mask, fled_short_mask;
+ u32 rfault = 0;
+ int ret;
+
+ ret = regmap_read(priv->regmap, MT6360_REG_CHGSTAT2, &chg_stat);
+ if (ret)
+ return ret;
+
+ ret = regmap_raw_read(priv->regmap, MT6360_REG_FLEDSTAT1, &fled_stat,
sizeof(fled_stat));
+ if (ret)
+ return ret;
+
+ if (led->led_no == MT6360_LED_FLASH1) {
+ strobe_timeout_mask = MT6360_FLED1STRBTO_MASK;
+ fled_short_mask = MT6360_FLED1SHORT_MASK;
+
+ } else {
+ strobe_timeout_mask = MT6360_FLED2STRBTO_MASK;
+ fled_short_mask = MT6360_FLED2SHORT_MASK;
+ }
+
+ if (chg_stat & MT6360_FLEDCHGVINOVP_MASK)
+ rfault |= LED_FAULT_INPUT_VOLTAGE;
+
+ if (fled_stat & strobe_timeout_mask)
+ rfault |= LED_FAULT_TIMEOUT;
+
+ if (fled_stat & fled_short_mask)
+ rfault |= LED_FAULT_SHORT_CIRCUIT;
+
+ if (fled_stat & MT6360_FLEDLVF_MASK)
+ rfault |= LED_FAULT_UNDER_VOLTAGE;
+
+ *fault = rfault;
+ return 0;
+}
+
+static const struct led_flash_ops mt6360_flash_ops = {
+ .flash_brightness_set = mt6360_strobe_brightness_set,
+ .strobe_set = mt6360_strobe_set,
+ .strobe_get = mt6360_strobe_get,
+ .timeout_set = mt6360_timeout_set,
+ .fault_get = mt6360_fault_get,
+};
+
+static int mt6360_isnk_init_default_state(struct mt6360_led *led)
+{
+ struct mt6360_priv *priv = led->priv;
+ unsigned int regval;
+ u32 level;
+ int ret;
+
+ ret = regmap_read(priv->regmap, MT6360_REG_ISNK(led->led_no), ®val);
+ if (ret)
+ return ret;
+ level = regval & MT6360_ISNK_MASK;
+
+ ret = regmap_read(priv->regmap, MT6360_REG_RGBEN, ®val);
+ if (ret)
+ return ret;
+
+ if (regval & MT6360_ISNK_ENMASK(led->led_no))
+ level += 1;
+ else
+ level = LED_OFF;
+
+ switch (led->default_state) {
+ case STATE_ON:
+ led->isnk.brightness = led->isnk.max_brightness;
+ break;
+ case STATE_KEEP:
+ led->isnk.brightness = min(level, led->isnk.max_brightness);
+ break;
+ default:
+ led->isnk.brightness = LED_OFF;
+ }
+
+ return mt6360_isnk_brightness_set(&led->isnk, led->isnk.brightness);
+}
+
+static int mt6360_isnk_register(struct device *parent, struct mt6360_led *led,
+ struct led_init_data *init_data)
+{
+ struct mt6360_priv *priv = led->priv;
+ int ret;
+
+ if (led->led_no == MT6360_LED_ISNK1) {
+ /* change isink to sw mode */
+ ret = regmap_update_bits(priv->regmap, MT6360_REG_RGBEN,
MT6360_CHRINDSEL_MASK,
+ MT6360_CHRINDSEL_MASK);
+ if (ret) {
+ dev_err(parent, "Failed to config ISNK1 to SW mode\n");
+ return ret;
+ }
+ }
+
+ ret = mt6360_isnk_init_default_state(led);
+ if (ret) {
+ dev_err(parent, "Failed to init %d isnk state\n", led->led_no);
+ return ret;
+ }
+
+ ret = devm_led_classdev_register_ext(parent, &led->isnk, init_data);
+ if (ret) {
+ dev_err(parent, "Couldn't register isink %d\n", led->led_no);
+ return ret;
+ }
+
+ return 0;
+}
+
+static int mt6360_flash_init_default_state(struct mt6360_led *led)
+{
+ struct led_classdev_flash *flash = &led->flash;
+ struct mt6360_priv *priv = led->priv;
+ u32 enable_mask = MT6360_TORCHEN_MASK | MT6360_FLCSEN_MASK(led->led_no);
+ u32 level;
+ unsigned int regval;
+ int ret;
+
+ ret = regmap_read(priv->regmap, MT6360_REG_FLEDITOR(led->led_no),
®val);
+ if (ret)
+ return ret;
+ level = regval & MT6360_ITORCH_MASK;
+
+ ret = regmap_read(priv->regmap, MT6360_REG_FLEDEN, ®val);
+ if (ret)
+ return ret;
+
+ if ((regval & enable_mask) == enable_mask)
+ level += 1;
+ else
+ level = LED_OFF;
+
+ switch (led->default_state) {
+ case STATE_ON:
+ flash->led_cdev.brightness = flash->led_cdev.max_brightness;
+ break;
+ case STATE_KEEP:
+ flash->led_cdev.brightness = min(level,
flash->led_cdev.max_brightness);
+ break;
+ default:
+ flash->led_cdev.brightness = LED_OFF;
+ }
+
+ return mt6360_torch_brightness_set(&flash->led_cdev,
flash->led_cdev.brightness);
+}
+
+#if IS_ENABLED(CONFIG_V4L2_FLASH_LED_CLASS)
+static int mt6360_flash_external_strobe_set(struct v4l2_flash *v4l2_flash,
bool enable)
+{
+ struct led_classdev_flash *flash = v4l2_flash->fled_cdev;
+ struct mt6360_led *led = container_of(flash, struct mt6360_led, flash);
+ struct mt6360_priv *priv = led->priv;
+ u32 enable_mask = MT6360_FLCSEN_MASK(led->led_no);
+ int ret;
+
+ ret = regmap_update_bits(priv->regmap, MT6360_REG_FLEDEN, enable_mask,
+ enable ? enable_mask : 0);
+ if (ret)
+ return ret;
+
+ if (enable)
+ priv->fled_strobe_used |= BIT(led->led_no);
+ else
+ priv->fled_strobe_used &= (~BIT(led->led_no));
+
+ return 0;
+}
+
+static const struct v4l2_flash_ops v4l2_flash_ops = {
+ .external_strobe_set = mt6360_flash_external_strobe_set,
+};
+
+static void mt6360_flash_init_v4l2_config(struct mt6360_led *led, struct
v4l2_flash_config *config)
+{
+ struct led_classdev_flash *flash = &led->flash;
+ struct led_classdev *lcdev = &flash->led_cdev;
+ struct led_flash_setting *s = &config->intensity;
+
+ snprintf(config->dev_name, sizeof(config->dev_name), "%s", lcdev->name);
+
+ s->min = MT6360_ITORCH_MIN;
+ s->step = MT6360_ITORCH_STEP;
+ s->val = s->max = (s->min) + (lcdev->max_brightness - 1) * s->step;
+
+ config->has_external_strobe = 1;
+}
+#else
+static const struct v4l2_flash_ops v4l2_flash_ops;
+
+static void mt6360_flash_init_v4l2_config(struct mt6360_led *led, struct
v4l2_flash_config *config)
+{
+}
+#endif
+
+static int mt6360_flash_register(struct device *parent, struct mt6360_led *led,
+ struct led_init_data *init_data)
+{
+ struct v4l2_flash_config v4l2_config = {0};
+ int ret;
+
+ ret = mt6360_flash_init_default_state(led);
+ if (ret) {
+ dev_err(parent, "Failed to init %d flash state\n", led->led_no);
+ return ret;
+ }
+
+ ret = devm_led_classdev_flash_register_ext(parent, &led->flash,
init_data);
+ if (ret) {
+ dev_err(parent, "Couldn't register flash %d\n", led->led_no);
+ return ret;
+ }
+
+ mt6360_flash_init_v4l2_config(led, &v4l2_config);
+ led->v4l2_flash = v4l2_flash_init(parent, init_data->fwnode, &led->flash,
&v4l2_flash_ops,
+ &v4l2_config);
+ if (IS_ERR(led->v4l2_flash)) {
+ dev_err(parent, "Failed to register %d v4l2 sd\n", led->led_no);
+ return PTR_ERR(led->v4l2_flash);
+ }
+
+ return 0;
+}
+
+static int mt6360_init_isnk_properties(struct mt6360_led *led, struct
led_init_data *init_data)
+{
+ struct led_classdev *isnk = &led->isnk;
+
+ if (led->led_no == MT6360_LED_ISNK4)
+ isnk->max_brightness = MT6360_ISNK4_MAXLEVEL;
+ else
+ isnk->max_brightness = MT6360_ISNK1_MAXLEVEL;
+
+ isnk->brightness_set_blocking = mt6360_isnk_brightness_set;
+
+ fwnode_property_read_string(init_data->fwnode, "linux,default-trigger",
+ &isnk->default_trigger);
+
+ return 0;
+}
+
+static void clamp_align(u32 *v, u32 min, u32 max, u32 step)
+{
+ *v = clamp_val(*v, min, max);
+ if (step > 1)
+ *v = (*v - min) / step * step + min;
+}
+
+static int mt6360_init_flash_properties(struct mt6360_led *led, struct
led_init_data *init_data)
+{
+ struct led_classdev_flash *flash = &led->flash;
+ struct led_classdev *lcdev = &flash->led_cdev;
+ struct led_flash_setting *s;
+ u32 val;
+ int ret;
+
+ ret = fwnode_property_read_u32(init_data->fwnode, "led-max-microamp",
&val);
+ if (ret)
+ val = MT6360_ITORCH_MIN;
+ else
+ clamp_align(&val, MT6360_ITORCH_MIN, MT6360_ITORCH_MAX,
MT6360_ITORCH_STEP);
+
+ lcdev->max_brightness = (val - MT6360_ITORCH_MIN) / MT6360_ITORCH_STEP
+ 1;
+ lcdev->brightness_set_blocking = mt6360_torch_brightness_set;
+ lcdev->flags |= LED_DEV_CAP_FLASH;
+
+ ret = fwnode_property_read_u32(init_data->fwnode, "flash-max-microamp",
&val);
+ if (ret)
+ val = MT6360_ISTRB_MIN;
+ else
+ clamp_align(&val, MT6360_ISTRB_MIN, MT6360_ISTRB_MAX,
MT6360_ISTRB_STEP);
+
+ s = &flash->brightness;
+ s->min = MT6360_ISTRB_MIN;
+ s->step = MT6360_ISTRB_STEP;
+ s->val = s->max = val;
+
+ /* always configure as min level when off to prevent flash current
spike */
+ ret = _mt6360_strobe_brightness_set(flash, s->min);
+ if (ret)
+ return ret;
+
+ ret = fwnode_property_read_u32(init_data->fwnode, "flash-max-timeout-us",
&val);
+ if (ret)
+ val = MT6360_STRBTO_MIN;
+ else
+ clamp_align(&val, MT6360_STRBTO_MIN, MT6360_STRBTO_MAX,
MT6360_STRBTO_STEP);
+
+ s = &flash->timeout;
+ s->min = MT6360_STRBTO_MIN;
+ s->step = MT6360_STRBTO_STEP;
+ s->val = s->max = val;
+
+ flash->ops = &mt6360_flash_ops;
+
+ return 0;
+}
+
+static int mt6360_init_common_properties(struct mt6360_led *led, struct
led_init_data *init_data)
+{
+ const char *str;
+
+ if (!fwnode_property_read_string(init_data->fwnode, "default-state",
&str)) {
+ if (!strcmp(str, "on"))
+ led->default_state = STATE_ON;
+ else if (!strcmp(str, "keep"))
+ led->default_state = STATE_KEEP;
+ else
+ led->default_state = STATE_OFF;
+ }
+
+ return 0;
+}
+
+static int mt6360_led_probe(struct platform_device *pdev)
+{
+ struct mt6360_priv *priv;
+ struct fwnode_handle *child;
+ int i, ret;
+
+ ret = device_get_child_node_count(&pdev->dev);
+ if (!ret || ret > MT6360_MAX_LEDS)
+ return -EINVAL;
+
+ priv = devm_kzalloc(&pdev->dev, sizeof(*priv), GFP_KERNEL);
+ if (!priv)
+ return -ENOMEM;
+
+ priv->dev = &pdev->dev;
+
+ priv->regmap = dev_get_regmap(pdev->dev.parent, NULL);
+ if (!priv->regmap) {
+ dev_err(&pdev->dev, "Failed to get parent regmap\n");
+ return -ENODEV;
+ }
+
+ device_for_each_child_node(&pdev->dev, child) {
+ struct mt6360_led *led;
+ struct led_init_data init_data = { .fwnode = child, };
+ u32 reg;
+
+ ret = fwnode_property_read_u32(child, "reg", ®);
+ if (ret || reg >= MT6360_MAX_LEDS || priv->leds[reg]) {
+ ret = -EINVAL;
+ goto out;
+ }
+
+ led = devm_kzalloc(&pdev->dev, sizeof(*led), GFP_KERNEL);