+ }
+ if (fled_id == FLED2 || led->iout_joint) {
+ iout2_reg = max77693_led_iout_to_reg(iout[FLED2]);
+ led->torch_iout_reg &= 0x0f;
+ }
+
+ led->torch_iout_reg |= ((iout1_reg << TORCH_IOUT1_SHIFT) |
+ (iout2_reg << TORCH_IOUT2_SHIFT));
+
+ return regmap_write(rmap, MAX77693_LED_REG_ITORCH,
+ led->torch_iout_reg);
+}
+
+static int max77693_set_flash_current(struct max77693_led_device *led,
+ int fled_id,
+ u32 micro_amp)
+{
+ struct max77693_led_config_data *cfg = led->cfg_data;
+ struct regmap *rmap = led->regmap;
+ u32 iout[2], iout_max[2];
+ u8 iout1_reg, iout2_reg;
+ int ret = -EINVAL;
+
+ iout_max[FLED1] = cfg->iout_flash[FLED1];
+ iout_max[FLED2] = cfg->iout_flash[FLED2];
+
+ led->allowed_modes |= MODE_FLASH_MASK;
+
+ max77693_calc_iout(led, fled_id, iout, micro_amp, iout_max,
+ FLASH);
+
+ if (fled_id == FLED1 || led->iout_joint) {
+ iout1_reg = max77693_led_iout_to_reg(iout[FLED1]);
+ ret = regmap_write(rmap, MAX77693_LED_REG_IFLASH1,
+ iout1_reg);
+ if (ret < 0)
+ return ret;
+ }
+ if (fled_id == FLED2 || led->iout_joint) {
+ iout2_reg = max77693_led_iout_to_reg(iout[FLED2]);
+ ret = regmap_write(rmap, MAX77693_LED_REG_IFLASH2,
+ iout2_reg);
+ }
+
+ return ret;
+}
+
+static int max77693_set_timeout(struct max77693_led_device *led, u32 microsec)
+{
+ struct max77693_led_config_data *cfg = led->cfg_data;
+ struct regmap *rmap = led->regmap;
+ u8 v;
+ int ret;
+
+ v = max77693_flash_timeout_to_reg(microsec);
+
+ if (cfg->trigger_type == MAX77693_LED_TRIG_TYPE_LEVEL)
+ v |= FLASH_TMR_LEVEL;
+
+ ret = regmap_write(rmap, MAX77693_LED_REG_FLASH_TIMER, v);
+ if (ret < 0)
+ return ret;
+
+ led->current_flash_timeout = microsec;
+
+ return 0;
+}
+
+static int max77693_strobe_status_get(struct max77693_led_device *led,
+ bool *state)
+{
+ struct regmap *rmap = led->regmap;
+ unsigned int v;
+ int ret;
+
+ ret = regmap_read(rmap, MAX77693_LED_REG_FLASH_STATUS, &v);
+ if (ret < 0)
+ return ret;
+
+ *state = v & FLASH_STATUS_FLASH_ON;
+
+ return ret;
+}
+
+static int max77693_int_flag_get(struct max77693_led_device *led,
+ unsigned int *v)
+{
+ struct regmap *rmap = led->regmap;
+
+ return regmap_read(rmap, MAX77693_LED_REG_FLASH_INT, v);
+}
+
+static int max77693_setup(struct max77693_led_device *led)
+{
+ struct max77693_led_config_data *cfg = led->cfg_data;
+ struct regmap *rmap = led->regmap;
+ int i, first_led, last_led, ret;
+ u32 max_flash_curr[2];
+ u8 v;
+
+ /*
+ * Initialize only flash current. Torch current doesn't
+ * require initialization as ITORCH register is written with
+ * new value each time brightness_set op is called.
+ */
+ if (led->iout_joint) {
+ first_led = FLED1;
+ last_led = FLED1;
+ max_flash_curr[FLED1] = cfg->iout_flash[FLED1] +
+ cfg->iout_flash[FLED2];
+ } else {
+ first_led = max77693_fled_used(led, FLED1) ? FLED1 : FLED2;
+ last_led = led->cfg_data->num_leds == 2 ? FLED2 : first_led;
+ max_flash_curr[FLED1] = cfg->iout_flash[FLED1];
+ max_flash_curr[FLED2] = cfg->iout_flash[FLED2];
+ }
+
+ for (i = first_led; i <= last_led; ++i) {
+ ret = max77693_set_flash_current(led, i,
+ max_flash_curr[i]);
+ if (ret < 0)
+ return ret;
+ }
+
+ v = TORCH_TMR_NO_TIMER | MAX77693_LED_TRIG_TYPE_LEVEL;
+ ret = regmap_write(rmap, MAX77693_LED_REG_ITORCHTIMER, v);
+ if (ret < 0)
+ return ret;
+
+ /* initially set FLED1 timeout */
+ ret = max77693_set_timeout(led, cfg->flash_timeout[FLED1]);
+ if (ret < 0)
+ return ret;
+
+ if (cfg->low_vsys > 0)
+ v = max77693_led_vsys_to_reg(cfg->low_vsys) |
+ MAX_FLASH1_MAX_FL_EN;
+ else
+ v = 0;
+
+ ret = regmap_write(rmap, MAX77693_LED_REG_MAX_FLASH1, v);
+ if (ret < 0)
+ return ret;
+ ret = regmap_write(rmap, MAX77693_LED_REG_MAX_FLASH2, 0);
+ if (ret < 0)
+ return ret;
+
+ if (cfg->boost_mode == MAX77693_LED_BOOST_FIXED)
+ v = FLASH_BOOST_FIXED;
+ else
+ v = cfg->boost_mode | cfg->boost_mode << 1;
+
+ if (max77693_fled_used(led, FLED1) && max77693_fled_used(led, FLED2))
+ v |= FLASH_BOOST_LEDNUM_2;
+
+ ret = regmap_write(rmap, MAX77693_LED_REG_VOUT_CNTL, v);
+ if (ret < 0)
+ return ret;
+
+ v = max77693_led_vout_to_reg(cfg->boost_vout);
+ ret = regmap_write(rmap, MAX77693_LED_REG_VOUT_FLASH1, v);
+ if (ret < 0)
+ return ret;
+
+ /* Allow all modes on both fled outputs */
+ led->allowed_modes = MODE_FLASH_MASK | MODE_TORCH_MASK;
+
+ return max77693_set_mode(led, MODE_OFF);
+}
+
+static int __max77693_led_brightness_set(struct max77693_led_device *led,
+ int fled_id, enum led_brightness value)
+{
+ int ret;
+
+ mutex_lock(&led->lock);
+
+ if (value == 0) {
+ ret = max77693_clear_mode(led, MODE_TORCH(fled_id));
+ if (ret < 0)
+ dev_dbg(&led->pdev->dev,
+ "Failed to clear torch mode (%d)\n",
+ ret);
+ goto unlock;
+ }
+
+ ret = max77693_set_torch_current(led, fled_id, value * TORCH_IOUT_STEP);
+ if (ret < 0) {
+ dev_dbg(&led->pdev->dev,
+ "Failed to set torch current (%d)\n",
+ ret);
+ goto unlock;
+ }
+
+ ret = max77693_add_mode(led, MODE_TORCH(fled_id));
+ if (ret < 0)
+ dev_dbg(&led->pdev->dev,
+ "Failed to set torch mode (%d)\n",
+ ret);
+unlock:
+ mutex_unlock(&led->lock);
+ return ret;
+}
+
+static void max77693_led_brightness_set_work(
+ struct work_struct *work)
+{
+ struct max77693_sub_led *sub_led =
+ container_of(work, struct max77693_sub_led,
+ work_brightness_set);
+ struct max77693_led_device *led = sub_led_to_led(sub_led);
+
+ __max77693_led_brightness_set(led, sub_led->fled_id,
+ sub_led->torch_brightness);
+}
+
+/* LED subsystem callbacks */
+
+static int max77693_led_brightness_set_sync(
+ struct led_classdev *led_cdev,
+ enum led_brightness value)
+{
+ struct led_classdev_flash *fled_cdev = lcdev_to_flcdev(led_cdev);
+ struct max77693_sub_led *sub_led = flcdev_to_sub_led(fled_cdev);
+ struct max77693_led_device *led = sub_led_to_led(sub_led);
+
+ return __max77693_led_brightness_set(led, sub_led->fled_id, value);
+}
+
+static void max77693_led_brightness_set(
+ struct led_classdev *led_cdev,
+ enum led_brightness value)
+{
+ struct led_classdev_flash *fled_cdev = lcdev_to_flcdev(led_cdev);
+ struct max77693_sub_led *sub_led = flcdev_to_sub_led(fled_cdev);
+
+ sub_led->torch_brightness = value;
+ schedule_work(&sub_led->work_brightness_set);
+}
+
+static int max77693_led_flash_brightness_set(
+ struct led_classdev_flash *fled_cdev,
+ u32 brightness)
+{
+ struct max77693_sub_led *sub_led = flcdev_to_sub_led(fled_cdev);
+ struct max77693_led_device *led = sub_led_to_led(sub_led);
+ int ret;
+
+ mutex_lock(&led->lock);
+ ret = max77693_set_flash_current(led, sub_led->fled_id, brightness);
+ mutex_unlock(&led->lock);
+
+ return ret;
+}
+
+static int max77693_led_flash_strobe_set(
+ struct led_classdev_flash *fled_cdev,
+ bool state)
+{
+ struct max77693_sub_led *sub_led = flcdev_to_sub_led(fled_cdev);
+ struct max77693_led_device *led = sub_led_to_led(sub_led);
+ int fled_id = sub_led->fled_id;
+ int ret;
+
+ mutex_lock(&led->lock);
+
+ if (!state) {
+ ret = max77693_clear_mode(led, MODE_FLASH(fled_id));
+ goto unlock;
+ }
+
+ if (sub_led->flash_timeout != led->current_flash_timeout) {
+ ret = max77693_set_timeout(led, sub_led->flash_timeout);
+ if (ret < 0)
+ goto unlock;
+ }
+
+ led->strobing_sub_led_id = fled_id;
+
+ ret = max77693_add_mode(led, MODE_FLASH(fled_id));
+
+unlock:
+ mutex_unlock(&led->lock);
+ return ret;
+}
+
+static int max77693_led_flash_fault_get(
+ struct led_classdev_flash *fled_cdev,
+ u32 *fault)
+{
+ struct max77693_sub_led *sub_led = flcdev_to_sub_led(fled_cdev);
+ struct max77693_led_device *led = sub_led_to_led(sub_led);
+ unsigned int v;
+ int ret;
+
+ ret = max77693_int_flag_get(led, &v);
+ if (ret < 0)
+ return ret;
+
+ *fault = 0;
+
+ if (v & ((sub_led->fled_id == FLED1) ? FLASH_INT_FLED1_OPEN :
+ FLASH_INT_FLED2_OPEN))
+ *fault |= LED_FAULT_OVER_VOLTAGE;
+ if (v & ((sub_led->fled_id == FLED1) ? FLASH_INT_FLED1_SHORT :
+ FLASH_INT_FLED2_SHORT))
+ *fault |= LED_FAULT_SHORT_CIRCUIT;
+ if (v & FLASH_INT_OVER_CURRENT)
+ *fault |= LED_FAULT_OVER_CURRENT;
+
+ return 0;
+}
+
+static int max77693_led_flash_strobe_get(
+ struct led_classdev_flash *fled_cdev,
+ bool *state)
+{
+ struct max77693_sub_led *sub_led = flcdev_to_sub_led(fled_cdev);
+ struct max77693_led_device *led = sub_led_to_led(sub_led);
+ int ret;
+
+ if (!state)
+ return -EINVAL;
+
+ mutex_lock(&led->lock);
+
+ ret = max77693_strobe_status_get(led, state);
+
+ *state = !!(*state && (led->strobing_sub_led_id == sub_led->fled_id));
+
+
+ mutex_unlock(&led->lock);
+
+ return ret;
+}
+
+static int max77693_led_flash_timeout_set(
+ struct led_classdev_flash *fled_cdev,
+ u32 timeout)
+{
+ struct max77693_sub_led *sub_led = flcdev_to_sub_led(fled_cdev);
+ struct max77693_led_device *led = sub_led_to_led(sub_led);
+
+ mutex_lock(&led->lock);
+ sub_led->flash_timeout = timeout;
+ mutex_unlock(&led->lock);
+
+ return 0;
+}
+
+static int max77693_led_parse_dt(struct max77693_led_device *led,
+ struct device_node *node)
+{
+ struct max77693_led_config_data *cfg = led->cfg_data;
+ struct max77693_sub_led *sub_leds = led->sub_leds;
+ struct device *dev = &led->pdev->dev;
+ struct device_node *child_node;
+ u32 led_sources[2];
+ int fled_id, ret;
+
+ of_property_read_u32(node, "maxim,trigger-type", &cfg->trigger_type);
+ of_property_read_u32(node, "maxim,boost-mode", &cfg->boost_mode);
+ of_property_read_u32(node, "maxim,boost-vout", &cfg->boost_vout);
+ of_property_read_u32(node, "maxim,vsys-min", &cfg->low_vsys);
+
+ for_each_available_child_of_node(node, child_node) {
+ ret = of_property_read_u32_array(child_node, "led-sources",
+ led_sources, 2);
+ if (ret < 0) {
+ dev_err(dev,
+ "Error reading \"led-sources\" DT property\n");
+ return ret;
+ }
+
+ if (led_sources[0] && led_sources[1]) {
+ fled_id = FLED1;
+ led->fled_mask = FLED1_IOUT | FLED2_IOUT;
+ } else if (led_sources[0] && !led_sources[1]) {
+ fled_id = FLED1;
+ led->fled_mask |= FLED1_IOUT;
+ } else if (!led_sources[0] && led_sources[1]) {
+ fled_id = FLED2;
+ led->fled_mask |= FLED2_IOUT;
+ } else {
+ dev_err(dev,
+ "Wrong \"led-sources\" DT property value\n");
+ return -EINVAL;
+ }
+
+ sub_leds[fled_id].fled_id = fled_id;
+
+ ret = of_property_read_string(child_node, "label",
+ (const char **) &cfg->label[fled_id]);
+ if (ret < 0) {
+ dev_err(dev, "Error reading \"label\" DT property\n");
+ return ret;
+ }
+
+ of_property_read_u32(child_node, "max-microamp",
+ &cfg->iout_torch[fled_id]);
+ of_property_read_u32(child_node, "flash-max-microamp",
+ &cfg->iout_flash[fled_id]);
+ of_property_read_u32(child_node, "flash-timeout-us",
+ &cfg->flash_timeout[fled_id]);
+
+ if (++led->cfg_data->num_leds == 2 ||
+ (max77693_fled_used(led, FLED1) &&
+ max77693_fled_used(led, FLED2)))
+ break;
+ }
+
+ 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 void max77693_led_validate_configuration(struct max77693_led_device *led)
+{
+ struct max77693_led_config_data *cfg = led->cfg_data;
+ int i;
+
+ if (led->cfg_data->num_leds == 1 &&
+ max77693_fled_used(led, FLED1) && max77693_fled_used(led, FLED2))
+ led->iout_joint = true;
+
+ cfg->boost_mode = clamp_val(cfg->boost_mode, MAX77693_LED_BOOST_NONE,
+ MAX77693_LED_BOOST_FIXED);
+
+ /* Boost must be enabled if both current outputs are used */
+ if ((cfg->boost_mode == MAX77693_LED_BOOST_NONE) && led->iout_joint)
+ cfg->boost_mode = MAX77693_LED_BOOST_FIXED;
+
+ /* Split max current settings to both outputs in case of joint leds */
+ if (led->iout_joint) {
+ cfg->iout_torch[FLED1] /= 2;
+ cfg->iout_torch[FLED2] = cfg->iout_torch[FLED1];
+ cfg->iout_flash[FLED1] /= 2;
+ cfg->iout_flash[FLED2] = cfg->iout_flash[FLED1];
+ }
+
+ for (i = FLED1; i <= FLED2; ++i) {
+ if (max77693_fled_used(led, i)) {
+ clamp_align(&cfg->iout_torch[i], TORCH_IOUT_MIN,
+ TORCH_IOUT_MAX, TORCH_IOUT_STEP);
+ clamp_align(&cfg->iout_flash[i], FLASH_IOUT_MIN,
+ cfg->boost_mode ? FLASH_IOUT_MAX_2LEDS :
+ FLASH_IOUT_MAX_1LED,
+ FLASH_IOUT_STEP);
+ } else {
+ cfg->iout_torch[i] = cfg->iout_flash[i] = 0;
+ }
+ }
+
+ for (i = 0; i < ARRAY_SIZE(cfg->flash_timeout); ++i)
+ clamp_align(&cfg->flash_timeout[i], FLASH_TIMEOUT_MIN,
+ FLASH_TIMEOUT_MAX, FLASH_TIMEOUT_STEP);
+
+ cfg->trigger_type = clamp_val(cfg->trigger_type,
+ MAX77693_LED_TRIG_TYPE_EDGE,
+ MAX77693_LED_TRIG_TYPE_LEVEL);
+
+ clamp_align(&cfg->boost_vout, FLASH_VOUT_MIN, FLASH_VOUT_MAX,
+ FLASH_VOUT_STEP);
+
+ if (cfg->low_vsys)
+ clamp_align(&cfg->low_vsys, MAX_FLASH1_VSYS_MIN,
+ MAX_FLASH1_VSYS_MAX, MAX_FLASH1_VSYS_STEP);
+}
+
+static int max77693_led_get_configuration(struct max77693_led_device *led)
+{
+ struct device *dev = &led->pdev->dev;
+ int ret;
+
+ if (!dev->of_node)
+ return -EINVAL;
+
+ led->cfg_data = devm_kzalloc(dev, sizeof(*led->cfg_data), GFP_KERNEL);
+ if (!led->cfg_data)
+ return -ENOMEM;
+
+ ret = max77693_led_parse_dt(led, dev->of_node);
+ if (ret < 0)
+ return ret;
+
+ max77693_led_validate_configuration(led);
+
+ return 0;
+}
+
+static const struct led_flash_ops flash_ops = {
+ .flash_brightness_set = max77693_led_flash_brightness_set,
+ .strobe_set = max77693_led_flash_strobe_set,
+ .strobe_get = max77693_led_flash_strobe_get,
+ .timeout_set = max77693_led_flash_timeout_set,
+ .fault_get = max77693_led_flash_fault_get,
+};
+
+static void max77693_init_flash_settings(struct max77693_led_device *led,
+ struct max77693_led_settings *s,
+ int fled_id)
+{
+ struct max77693_led_config_data *cfg = led->cfg_data;
+ struct led_flash_setting *setting;
+
+ /* Init torch intensity setting */
+ setting = &s->torch_brightness;
+ setting->min = TORCH_IOUT_MIN;
+ setting->max = cfg->iout_torch[fled_id];
+ setting->max = led->iout_joint ?
+ cfg->iout_torch[FLED1] + cfg->iout_torch[FLED2] :
+ cfg->iout_torch[fled_id];
+ setting->step = TORCH_IOUT_STEP;
+ setting->val = setting->max;
+
+ /* Init flash intensity setting */
+ setting = &s->flash_brightness;
+ setting->min = FLASH_IOUT_MIN;
+ setting->max = led->iout_joint ?
+ cfg->iout_flash[FLED1] + cfg->iout_flash[FLED2] :
+ cfg->iout_flash[fled_id];
+ setting->step = FLASH_IOUT_STEP;
+ setting->val = setting->max;
+
+ /* Init flash timeout setting */
+ setting = &s->flash_timeout;
+ setting->min = FLASH_TIMEOUT_MIN;
+ setting->max = cfg->flash_timeout[fled_id];
+ setting->step = FLASH_TIMEOUT_STEP;
+ setting->val = setting->max;
+}
+
+static int max77693_set_available_sync_led(struct max77693_led_device *led,
+ int fled_id)
+{
+ struct max77693_sub_led *sub_led = &led->sub_leds[fled_id];
+ struct led_classdev_flash *fled_cdev = &sub_led->fled_cdev;
+
+ fled_cdev->sync_leds = devm_kzalloc(&led->pdev->dev, sizeof(fled_cdev),
+ GFP_KERNEL);
+ if (!fled_cdev->sync_leds)
+ return -ENOMEM;
+
+ fled_cdev->sync_leds[0] = &led->sub_leds[!fled_id].fled_cdev;
+ fled_cdev->num_sync_leds = 1;
+
+ return 0;
+}
+
+static void max77693_init_fled_cdev(struct max77693_led_device *led,
+ int fled_id)
+{
+ struct led_classdev_flash *fled_cdev;
+ struct led_classdev *led_cdev;
+ struct max77693_sub_led *sub_led = &led->sub_leds[fled_id];
+ struct max77693_led_settings settings;
+
+ /* Initialize flash settings */
+ max77693_init_flash_settings(led, &settings, fled_id);
+
+ /* Initialize LED Flash class device */
+ fled_cdev = &sub_led->fled_cdev;
+ fled_cdev->ops = &flash_ops;
+ led_cdev = &fled_cdev->led_cdev;
+ led_cdev->name = led->cfg_data->label[fled_id];
+ led_cdev->brightness_set = max77693_led_brightness_set;
+ led_cdev->brightness_set_sync = max77693_led_brightness_set_sync;
+ INIT_WORK(&sub_led->work_brightness_set,
+ max77693_led_brightness_set_work);
+
+ led_cdev->max_brightness = settings.torch_brightness.val /
+ TORCH_IOUT_STEP;
+ led_cdev->flags |= LED_DEV_CAP_FLASH;
+ if (led->cfg_data->num_leds == 2)
+ led_cdev->flags |= LED_DEV_CAP_SYNC_STROBE;
+
+ fled_cdev->brightness = settings.flash_brightness;
+ fled_cdev->timeout = settings.flash_timeout;
+ sub_led->flash_timeout = fled_cdev->timeout.val;
+}
+
+static int max77693_led_probe(struct platform_device *pdev)
+{
+ struct device *dev = &pdev->dev;
+ struct max77693_dev *iodev = dev_get_drvdata(dev->parent);
+ struct max77693_led_device *led;
+ struct max77693_sub_led *sub_leds;
+ int init_fled_cdev[2], i, ret;
+
+ led = devm_kzalloc(dev, sizeof(*led), GFP_KERNEL);
+ if (!led)
+ return -ENOMEM;
+
+ led->pdev = pdev;
+ led->regmap = iodev->regmap;
+ sub_leds = led->sub_leds;
+
+ platform_set_drvdata(pdev, led);
+ ret = max77693_led_get_configuration(led);
+ if (ret < 0)
+ return ret;
+
+ ret = max77693_setup(led);
+ if (ret < 0)
+ return ret;
+
+ init_fled_cdev[FLED1] =
+ led->iout_joint || max77693_fled_used(led, FLED1);
+ init_fled_cdev[FLED2] =
+ !led->iout_joint && max77693_fled_used(led, FLED2);
+
+ /* Initialize LED Flash class device(s) */
+ for (i = FLED1; i <= FLED2; ++i)
+ if (init_fled_cdev[i])
+ max77693_init_fled_cdev(led, i);
+
+ /* Setup sub-leds available for flash strobe synchronization */
+ if (led->cfg_data->num_leds == 2) {
+ for (i = FLED1; i <= FLED2; ++i) {
+ ret = max77693_set_available_sync_led(led, i);
+ if (ret < 0)
+ return ret;
+ }
+ }
+
+ mutex_init(&led->lock);
+
+ /* Register LED Flash class device(s) */
+ for (i = FLED1; i <= FLED2; ++i) {
+ if (init_fled_cdev[i]) {