|  | // SPDX-License-Identifier: GPL-2.0-only | 
|  |  | 
|  | #include <linux/bitops.h> | 
|  | #include <linux/i2c.h> | 
|  | #include <linux/kernel.h> | 
|  | #include <linux/led-class-flash.h> | 
|  | #include <linux/module.h> | 
|  | #include <linux/mutex.h> | 
|  | #include <linux/property.h> | 
|  | #include <linux/regmap.h> | 
|  | #include <media/v4l2-flash-led-class.h> | 
|  |  | 
|  | #define RT4505_REG_RESET	0x0 | 
|  | #define RT4505_REG_CONFIG	0x8 | 
|  | #define RT4505_REG_ILED		0x9 | 
|  | #define RT4505_REG_ENABLE	0xA | 
|  | #define RT4505_REG_FLAGS	0xB | 
|  |  | 
|  | #define RT4505_RESET_MASK	BIT(7) | 
|  | #define RT4505_FLASHTO_MASK	GENMASK(2, 0) | 
|  | #define RT4505_ITORCH_MASK	GENMASK(7, 5) | 
|  | #define RT4505_ITORCH_SHIFT	5 | 
|  | #define RT4505_IFLASH_MASK	GENMASK(4, 0) | 
|  | #define RT4505_ENABLE_MASK	GENMASK(5, 0) | 
|  | #define RT4505_TORCH_SET	(BIT(0) | BIT(4)) | 
|  | #define RT4505_FLASH_SET	(BIT(0) | BIT(1) | BIT(2) | BIT(4)) | 
|  | #define RT4505_EXT_FLASH_SET	(BIT(0) | BIT(1) | BIT(4) | BIT(5)) | 
|  | #define RT4505_FLASH_GET	(BIT(0) | BIT(1) | BIT(4)) | 
|  | #define RT4505_OVP_MASK		BIT(3) | 
|  | #define RT4505_SHORT_MASK	BIT(2) | 
|  | #define RT4505_OTP_MASK		BIT(1) | 
|  | #define RT4505_TIMEOUT_MASK	BIT(0) | 
|  |  | 
|  | #define RT4505_ITORCH_MINUA	46000 | 
|  | #define RT4505_ITORCH_MAXUA	375000 | 
|  | #define RT4505_ITORCH_STPUA	47000 | 
|  | #define RT4505_IFLASH_MINUA	93750 | 
|  | #define RT4505_IFLASH_MAXUA	1500000 | 
|  | #define RT4505_IFLASH_STPUA	93750 | 
|  | #define RT4505_FLASHTO_MINUS	100000 | 
|  | #define RT4505_FLASHTO_MAXUS	800000 | 
|  | #define RT4505_FLASHTO_STPUS	100000 | 
|  |  | 
|  | struct rt4505_priv { | 
|  | struct device *dev; | 
|  | struct regmap *regmap; | 
|  | struct mutex lock; | 
|  | struct led_classdev_flash flash; | 
|  | struct v4l2_flash *v4l2_flash; | 
|  | }; | 
|  |  | 
|  | static int rt4505_torch_brightness_set(struct led_classdev *lcdev, | 
|  | enum led_brightness level) | 
|  | { | 
|  | struct rt4505_priv *priv = | 
|  | container_of(lcdev, struct rt4505_priv, flash.led_cdev); | 
|  | u32 val = 0; | 
|  | int ret; | 
|  |  | 
|  | mutex_lock(&priv->lock); | 
|  |  | 
|  | if (level != LED_OFF) { | 
|  | ret = regmap_update_bits(priv->regmap, | 
|  | RT4505_REG_ILED, RT4505_ITORCH_MASK, | 
|  | (level - 1) << RT4505_ITORCH_SHIFT); | 
|  | if (ret) | 
|  | goto unlock; | 
|  |  | 
|  | val = RT4505_TORCH_SET; | 
|  | } | 
|  |  | 
|  | ret = regmap_update_bits(priv->regmap, RT4505_REG_ENABLE, | 
|  | RT4505_ENABLE_MASK, val); | 
|  |  | 
|  | unlock: | 
|  | mutex_unlock(&priv->lock); | 
|  | return ret; | 
|  | } | 
|  |  | 
|  | static enum led_brightness rt4505_torch_brightness_get( | 
|  | struct led_classdev *lcdev) | 
|  | { | 
|  | struct rt4505_priv *priv = | 
|  | container_of(lcdev, struct rt4505_priv, flash.led_cdev); | 
|  | u32 val; | 
|  | int ret; | 
|  |  | 
|  | mutex_lock(&priv->lock); | 
|  |  | 
|  | ret = regmap_read(priv->regmap, RT4505_REG_ENABLE, &val); | 
|  | if (ret) { | 
|  | dev_err(lcdev->dev, "Failed to get LED enable\n"); | 
|  | ret = LED_OFF; | 
|  | goto unlock; | 
|  | } | 
|  |  | 
|  | if ((val & RT4505_ENABLE_MASK) != RT4505_TORCH_SET) { | 
|  | ret = LED_OFF; | 
|  | goto unlock; | 
|  | } | 
|  |  | 
|  | ret = regmap_read(priv->regmap, RT4505_REG_ILED, &val); | 
|  | if (ret) { | 
|  | dev_err(lcdev->dev, "Failed to get LED brightness\n"); | 
|  | ret = LED_OFF; | 
|  | goto unlock; | 
|  | } | 
|  |  | 
|  | ret = ((val & RT4505_ITORCH_MASK) >> RT4505_ITORCH_SHIFT) + 1; | 
|  |  | 
|  | unlock: | 
|  | mutex_unlock(&priv->lock); | 
|  | return ret; | 
|  | } | 
|  |  | 
|  | static int rt4505_flash_brightness_set(struct led_classdev_flash *fled_cdev, | 
|  | u32 brightness) | 
|  | { | 
|  | struct rt4505_priv *priv = | 
|  | container_of(fled_cdev, struct rt4505_priv, flash); | 
|  | struct led_flash_setting *s = &fled_cdev->brightness; | 
|  | u32 val = (brightness - s->min) / s->step; | 
|  | int ret; | 
|  |  | 
|  | mutex_lock(&priv->lock); | 
|  | ret = regmap_update_bits(priv->regmap, RT4505_REG_ILED, | 
|  | RT4505_IFLASH_MASK, val); | 
|  | mutex_unlock(&priv->lock); | 
|  |  | 
|  | return ret; | 
|  | } | 
|  |  | 
|  | static int rt4505_flash_strobe_set(struct led_classdev_flash *fled_cdev, | 
|  | bool state) | 
|  | { | 
|  | struct rt4505_priv *priv = | 
|  | container_of(fled_cdev, struct rt4505_priv, flash); | 
|  | u32 val = state ? RT4505_FLASH_SET : 0; | 
|  | int ret; | 
|  |  | 
|  | mutex_lock(&priv->lock); | 
|  | ret = regmap_update_bits(priv->regmap, RT4505_REG_ENABLE, | 
|  | RT4505_ENABLE_MASK, val); | 
|  | mutex_unlock(&priv->lock); | 
|  |  | 
|  | return ret; | 
|  | } | 
|  |  | 
|  | static int rt4505_flash_strobe_get(struct led_classdev_flash *fled_cdev, | 
|  | bool *state) | 
|  | { | 
|  | struct rt4505_priv *priv = | 
|  | container_of(fled_cdev, struct rt4505_priv, flash); | 
|  | u32 val; | 
|  | int ret; | 
|  |  | 
|  | mutex_lock(&priv->lock); | 
|  |  | 
|  | ret = regmap_read(priv->regmap, RT4505_REG_ENABLE, &val); | 
|  | if (ret) | 
|  | goto unlock; | 
|  |  | 
|  | *state = (val & RT4505_FLASH_GET) == RT4505_FLASH_GET; | 
|  |  | 
|  | unlock: | 
|  | mutex_unlock(&priv->lock); | 
|  | return ret; | 
|  | } | 
|  |  | 
|  | static int rt4505_flash_timeout_set(struct led_classdev_flash *fled_cdev, | 
|  | u32 timeout) | 
|  | { | 
|  | struct rt4505_priv *priv = | 
|  | container_of(fled_cdev, struct rt4505_priv, flash); | 
|  | struct led_flash_setting *s = &fled_cdev->timeout; | 
|  | u32 val = (timeout - s->min) / s->step; | 
|  | int ret; | 
|  |  | 
|  | mutex_lock(&priv->lock); | 
|  | ret = regmap_update_bits(priv->regmap, RT4505_REG_CONFIG, | 
|  | RT4505_FLASHTO_MASK, val); | 
|  | mutex_unlock(&priv->lock); | 
|  |  | 
|  | return ret; | 
|  | } | 
|  |  | 
|  | static int rt4505_fault_get(struct led_classdev_flash *fled_cdev, u32 *fault) | 
|  | { | 
|  | struct rt4505_priv *priv = | 
|  | container_of(fled_cdev, struct rt4505_priv, flash); | 
|  | u32 val, led_faults = 0; | 
|  | int ret; | 
|  |  | 
|  | ret = regmap_read(priv->regmap, RT4505_REG_FLAGS, &val); | 
|  | if (ret) | 
|  | return ret; | 
|  |  | 
|  | if (val & RT4505_OVP_MASK) | 
|  | led_faults |= LED_FAULT_OVER_VOLTAGE; | 
|  |  | 
|  | if (val & RT4505_SHORT_MASK) | 
|  | led_faults |= LED_FAULT_SHORT_CIRCUIT; | 
|  |  | 
|  | if (val & RT4505_OTP_MASK) | 
|  | led_faults |= LED_FAULT_OVER_TEMPERATURE; | 
|  |  | 
|  | if (val & RT4505_TIMEOUT_MASK) | 
|  | led_faults |= LED_FAULT_TIMEOUT; | 
|  |  | 
|  | *fault = led_faults; | 
|  | return 0; | 
|  | } | 
|  |  | 
|  | static const struct led_flash_ops rt4505_flash_ops = { | 
|  | .flash_brightness_set = rt4505_flash_brightness_set, | 
|  | .strobe_set = rt4505_flash_strobe_set, | 
|  | .strobe_get = rt4505_flash_strobe_get, | 
|  | .timeout_set = rt4505_flash_timeout_set, | 
|  | .fault_get = rt4505_fault_get, | 
|  | }; | 
|  |  | 
|  | static bool rt4505_is_accessible_reg(struct device *dev, unsigned int reg) | 
|  | { | 
|  | if (reg == RT4505_REG_RESET || | 
|  | (reg >= RT4505_REG_CONFIG && reg <= RT4505_REG_FLAGS)) | 
|  | return true; | 
|  | return false; | 
|  | } | 
|  |  | 
|  | static const struct regmap_config rt4505_regmap_config = { | 
|  | .reg_bits = 8, | 
|  | .val_bits = 8, | 
|  | .max_register = RT4505_REG_FLAGS, | 
|  |  | 
|  | .readable_reg = rt4505_is_accessible_reg, | 
|  | .writeable_reg = rt4505_is_accessible_reg, | 
|  | }; | 
|  |  | 
|  | #if IS_ENABLED(CONFIG_V4L2_FLASH_LED_CLASS) | 
|  | static int rt4505_flash_external_strobe_set(struct v4l2_flash *v4l2_flash, | 
|  | bool enable) | 
|  | { | 
|  | struct led_classdev_flash *flash = v4l2_flash->fled_cdev; | 
|  | struct rt4505_priv *priv = | 
|  | container_of(flash, struct rt4505_priv, flash); | 
|  | u32 val = enable ? RT4505_EXT_FLASH_SET : 0; | 
|  | int ret; | 
|  |  | 
|  | mutex_lock(&priv->lock); | 
|  | ret = regmap_update_bits(priv->regmap, RT4505_REG_ENABLE, | 
|  | RT4505_ENABLE_MASK, val); | 
|  | mutex_unlock(&priv->lock); | 
|  |  | 
|  | return ret; | 
|  | } | 
|  |  | 
|  | static const struct v4l2_flash_ops v4l2_flash_ops = { | 
|  | .external_strobe_set = rt4505_flash_external_strobe_set, | 
|  | }; | 
|  |  | 
|  | static void rt4505_init_v4l2_config(struct rt4505_priv *priv, | 
|  | struct v4l2_flash_config *config) | 
|  | { | 
|  | struct led_classdev_flash *flash = &priv->flash; | 
|  | struct led_classdev *lcdev = &flash->led_cdev; | 
|  | struct led_flash_setting *s; | 
|  |  | 
|  | strscpy(config->dev_name, lcdev->dev->kobj.name, | 
|  | sizeof(config->dev_name)); | 
|  |  | 
|  | s = &config->intensity; | 
|  | s->min = RT4505_ITORCH_MINUA; | 
|  | s->step = RT4505_ITORCH_STPUA; | 
|  | s->max = s->val = s->min + (lcdev->max_brightness - 1) * s->step; | 
|  |  | 
|  | config->flash_faults = LED_FAULT_OVER_VOLTAGE | | 
|  | LED_FAULT_SHORT_CIRCUIT | | 
|  | LED_FAULT_LED_OVER_TEMPERATURE | | 
|  | LED_FAULT_TIMEOUT; | 
|  | config->has_external_strobe = 1; | 
|  | } | 
|  | #else | 
|  | static const struct v4l2_flash_ops v4l2_flash_ops; | 
|  | static void rt4505_init_v4l2_config(struct rt4505_priv *priv, | 
|  | struct v4l2_flash_config *config) | 
|  | { | 
|  | } | 
|  | #endif | 
|  |  | 
|  | static void rt4505_init_flash_properties(struct rt4505_priv *priv, | 
|  | struct fwnode_handle *child) | 
|  | { | 
|  | struct led_classdev_flash *flash = &priv->flash; | 
|  | struct led_classdev *lcdev = &flash->led_cdev; | 
|  | struct led_flash_setting *s; | 
|  | u32 val; | 
|  | int ret; | 
|  |  | 
|  | ret = fwnode_property_read_u32(child, "led-max-microamp", &val); | 
|  | if (ret) { | 
|  | dev_warn(priv->dev, "led-max-microamp DT property missing\n"); | 
|  | val = RT4505_ITORCH_MINUA; | 
|  | } else | 
|  | val = clamp_val(val, RT4505_ITORCH_MINUA, RT4505_ITORCH_MAXUA); | 
|  |  | 
|  | lcdev->max_brightness = | 
|  | (val - RT4505_ITORCH_MINUA) / RT4505_ITORCH_STPUA + 1; | 
|  | lcdev->brightness_set_blocking = rt4505_torch_brightness_set; | 
|  | lcdev->brightness_get = rt4505_torch_brightness_get; | 
|  | lcdev->flags |= LED_DEV_CAP_FLASH; | 
|  |  | 
|  | ret = fwnode_property_read_u32(child, "flash-max-microamp", &val); | 
|  | if (ret) { | 
|  | dev_warn(priv->dev, "flash-max-microamp DT property missing\n"); | 
|  | val = RT4505_IFLASH_MINUA; | 
|  | } else | 
|  | val = clamp_val(val, RT4505_IFLASH_MINUA, RT4505_IFLASH_MAXUA); | 
|  |  | 
|  | s = &flash->brightness; | 
|  | s->min = RT4505_IFLASH_MINUA; | 
|  | s->step = RT4505_IFLASH_STPUA; | 
|  | s->max = s->val = val; | 
|  |  | 
|  | ret = fwnode_property_read_u32(child, "flash-max-timeout-us", &val); | 
|  | if (ret) { | 
|  | dev_warn(priv->dev, | 
|  | "flash-max-timeout-us DT property missing\n"); | 
|  | val = RT4505_FLASHTO_MINUS; | 
|  | } else | 
|  | val = clamp_val(val, RT4505_FLASHTO_MINUS, | 
|  | RT4505_FLASHTO_MAXUS); | 
|  |  | 
|  | s = &flash->timeout; | 
|  | s->min = RT4505_FLASHTO_MINUS; | 
|  | s->step = RT4505_FLASHTO_STPUS; | 
|  | s->max = s->val = val; | 
|  |  | 
|  | flash->ops = &rt4505_flash_ops; | 
|  | } | 
|  |  | 
|  | static int rt4505_probe(struct i2c_client *client) | 
|  | { | 
|  | struct rt4505_priv *priv; | 
|  | struct fwnode_handle *child; | 
|  | struct led_init_data init_data = {}; | 
|  | struct v4l2_flash_config v4l2_config = {}; | 
|  | int ret; | 
|  |  | 
|  | priv = devm_kzalloc(&client->dev, sizeof(*priv), GFP_KERNEL); | 
|  | if (!priv) | 
|  | return -ENOMEM; | 
|  |  | 
|  | priv->dev = &client->dev; | 
|  | mutex_init(&priv->lock); | 
|  |  | 
|  | priv->regmap = devm_regmap_init_i2c(client, &rt4505_regmap_config); | 
|  | if (IS_ERR(priv->regmap)) { | 
|  | dev_err(priv->dev, "Failed to allocate register map\n"); | 
|  | return PTR_ERR(priv->regmap); | 
|  | } | 
|  |  | 
|  | ret = regmap_write(priv->regmap, RT4505_REG_RESET, RT4505_RESET_MASK); | 
|  | if (ret) { | 
|  | dev_err(priv->dev, "Failed to reset registers\n"); | 
|  | return ret; | 
|  | } | 
|  |  | 
|  | child = fwnode_get_next_available_child_node(client->dev.fwnode, NULL); | 
|  | if (!child) { | 
|  | dev_err(priv->dev, "Failed to get child node\n"); | 
|  | return -EINVAL; | 
|  | } | 
|  | init_data.fwnode = child; | 
|  |  | 
|  | rt4505_init_flash_properties(priv, child); | 
|  | ret = devm_led_classdev_flash_register_ext(priv->dev, &priv->flash, | 
|  | &init_data); | 
|  | if (ret) { | 
|  | dev_err(priv->dev, "Failed to register flash\n"); | 
|  | return ret; | 
|  | } | 
|  |  | 
|  | rt4505_init_v4l2_config(priv, &v4l2_config); | 
|  | priv->v4l2_flash = v4l2_flash_init(priv->dev, init_data.fwnode, | 
|  | &priv->flash, &v4l2_flash_ops, | 
|  | &v4l2_config); | 
|  | if (IS_ERR(priv->v4l2_flash)) { | 
|  | dev_err(priv->dev, "Failed to register v4l2 flash\n"); | 
|  | return PTR_ERR(priv->v4l2_flash); | 
|  | } | 
|  |  | 
|  | i2c_set_clientdata(client, priv); | 
|  | return 0; | 
|  | } | 
|  |  | 
|  | static int rt4505_remove(struct i2c_client *client) | 
|  | { | 
|  | struct rt4505_priv *priv = i2c_get_clientdata(client); | 
|  |  | 
|  | v4l2_flash_release(priv->v4l2_flash); | 
|  | return 0; | 
|  | } | 
|  |  | 
|  | static void rt4505_shutdown(struct i2c_client *client) | 
|  | { | 
|  | struct rt4505_priv *priv = i2c_get_clientdata(client); | 
|  |  | 
|  | /* Reset registers to make sure all off before shutdown */ | 
|  | regmap_write(priv->regmap, RT4505_REG_RESET, RT4505_RESET_MASK); | 
|  | } | 
|  |  | 
|  | static const struct of_device_id __maybe_unused rt4505_leds_match[] = { | 
|  | { .compatible = "richtek,rt4505", }, | 
|  | {} | 
|  | }; | 
|  | MODULE_DEVICE_TABLE(of, rt4505_leds_match); | 
|  |  | 
|  | static struct i2c_driver rt4505_driver = { | 
|  | .driver = { | 
|  | .name = "rt4505", | 
|  | .of_match_table = of_match_ptr(rt4505_leds_match), | 
|  | }, | 
|  | .probe_new = rt4505_probe, | 
|  | .remove = rt4505_remove, | 
|  | .shutdown = rt4505_shutdown, | 
|  | }; | 
|  | module_i2c_driver(rt4505_driver); | 
|  |  | 
|  | MODULE_AUTHOR("ChiYuan Huang <cy_huang@richtek.com>"); | 
|  | MODULE_LICENSE("GPL v2"); |