1 // SPDX-License-Identifier: GPL-2.0 2 /* 3 * Flash and Torch LED Driver for Samsung S2M series PMICs. 4 * 5 * Copyright (c) 2015 Samsung Electronics Co., Ltd 6 * Copyright (c) 2026 Kaustabh Chakraborty <kauschluss@disroot.org> 7 */ 8 9 #include <linux/container_of.h> 10 #include <linux/led-class-flash.h> 11 #include <linux/mfd/samsung/core.h> 12 #include <linux/mfd/samsung/s2mu005.h> 13 #include <linux/module.h> 14 #include <linux/of.h> 15 #include <linux/platform_device.h> 16 #include <linux/regmap.h> 17 #include <media/v4l2-flash-led-class.h> 18 19 #define MAX_CHANNELS 2 20 21 struct s2m_led { 22 struct regmap *regmap; 23 struct led_classdev_flash fled; 24 struct v4l2_flash *v4l2_flash; 25 /* 26 * The mutex object prevents the concurrent access of flash control 27 * registers by the LED and V4L2 subsystems. 28 */ 29 struct mutex lock; 30 unsigned int reg_enable; 31 u8 channel; 32 u8 flash_brightness; 33 u8 flash_timeout; 34 }; 35 36 static struct s2m_led *to_s2m_led(struct led_classdev_flash *fled) 37 { 38 return container_of(fled, struct s2m_led, fled); 39 } 40 41 static struct led_classdev_flash *to_s2m_fled(struct led_classdev *cdev) 42 { 43 return container_of(cdev, struct led_classdev_flash, led_cdev); 44 } 45 46 static int s2m_fled_flash_brightness_set(struct led_classdev_flash *fled, u32 brightness) 47 { 48 struct s2m_led *led = to_s2m_led(fled); 49 struct led_flash_setting *setting = &fled->brightness; 50 51 mutex_lock(&led->lock); 52 led->flash_brightness = (brightness - setting->min) / setting->step; 53 mutex_unlock(&led->lock); 54 55 return 0; 56 } 57 58 static int s2m_fled_flash_timeout_set(struct led_classdev_flash *fled, u32 timeout) 59 { 60 struct s2m_led *led = to_s2m_led(fled); 61 struct led_flash_setting *setting = &fled->timeout; 62 63 mutex_lock(&led->lock); 64 led->flash_timeout = (timeout - setting->min) / setting->step; 65 mutex_unlock(&led->lock); 66 67 return 0; 68 } 69 70 #if IS_ENABLED(CONFIG_V4L2_FLASH_LED_CLASS) 71 static int s2m_fled_flash_external_strobe_set(struct v4l2_flash *v4l2_flash, bool enable) 72 { 73 struct s2m_led *led = to_s2m_led(v4l2_flash->fled_cdev); 74 75 return led->fled.ops->strobe_set(&led->fled, enable); 76 } 77 78 static const struct v4l2_flash_ops s2m_fled_v4l2_flash_ops = { 79 .external_strobe_set = s2m_fled_flash_external_strobe_set, 80 }; 81 #else 82 static const struct v4l2_flash_ops s2m_fled_v4l2_flash_ops; 83 #endif 84 85 static void s2m_fled_v4l2_flash_release(void *v4l2_flash) 86 { 87 v4l2_flash_release(v4l2_flash); 88 } 89 90 static int s2mu005_fled_torch_brightness_set(struct led_classdev *cdev, enum led_brightness value) 91 { 92 struct s2m_led *led = to_s2m_led(to_s2m_fled(cdev)); 93 int ret; 94 95 mutex_lock(&led->lock); 96 97 if (!value) { 98 ret = regmap_clear_bits(led->regmap, led->reg_enable, 99 S2MU005_FLED_TORCH_EN(led->channel)); 100 if (ret) 101 dev_err(cdev->dev, "failed to disable torch LED\n"); 102 goto unlock; 103 } 104 105 ret = regmap_update_bits(led->regmap, S2MU005_REG_FLED_CH_CTRL1(led->channel), 106 S2MU005_FLED_TORCH_IOUT, 107 FIELD_PREP(S2MU005_FLED_TORCH_IOUT, value - 1)); 108 if (ret) { 109 dev_err(cdev->dev, "failed to set torch current\n"); 110 goto unlock; 111 } 112 113 ret = regmap_set_bits(led->regmap, led->reg_enable, S2MU005_FLED_TORCH_EN(led->channel)); 114 if (ret) { 115 dev_err(cdev->dev, "failed to enable torch LED\n"); 116 goto unlock; 117 } 118 119 unlock: 120 mutex_unlock(&led->lock); 121 122 return ret; 123 } 124 125 static int s2mu005_fled_flash_strobe_set(struct led_classdev_flash *fled, bool state) 126 { 127 struct s2m_led *led = to_s2m_led(fled); 128 int ret; 129 130 mutex_lock(&led->lock); 131 132 ret = regmap_clear_bits(led->regmap, led->reg_enable, S2MU005_FLED_FLASH_EN(led->channel)); 133 if (ret) { 134 dev_err(fled->led_cdev.dev, "failed to disable flash LED\n"); 135 goto unlock; 136 } 137 138 if (!state) 139 goto unlock; 140 141 ret = regmap_update_bits(led->regmap, S2MU005_REG_FLED_CH_CTRL0(led->channel), 142 S2MU005_FLED_FLASH_IOUT, 143 FIELD_PREP(S2MU005_FLED_FLASH_IOUT, led->flash_brightness)); 144 if (ret) { 145 dev_err(fled->led_cdev.dev, "failed to set flash brightness\n"); 146 goto unlock; 147 } 148 149 ret = regmap_update_bits(led->regmap, S2MU005_REG_FLED_CH_CTRL3(led->channel), 150 S2MU005_FLED_FLASH_TIMEOUT, 151 FIELD_PREP(S2MU005_FLED_FLASH_TIMEOUT, led->flash_timeout)); 152 if (ret) { 153 dev_err(fled->led_cdev.dev, "failed to set flash timeout\n"); 154 goto unlock; 155 } 156 157 ret = regmap_set_bits(led->regmap, led->reg_enable, S2MU005_FLED_FLASH_EN(led->channel)); 158 if (ret) { 159 dev_err(fled->led_cdev.dev, "failed to enable flash LED\n"); 160 goto unlock; 161 } 162 163 unlock: 164 mutex_unlock(&led->lock); 165 166 return ret; 167 } 168 169 static int s2mu005_fled_flash_strobe_get(struct led_classdev_flash *fled, bool *state) 170 { 171 struct s2m_led *led = to_s2m_led(fled); 172 u32 val; 173 int ret; 174 175 mutex_lock(&led->lock); 176 177 ret = regmap_read(led->regmap, S2MU005_REG_FLED_STATUS, &val); 178 if (ret) { 179 dev_err(fled->led_cdev.dev, "failed to fetch LED status\n"); 180 goto unlock; 181 } 182 183 *state = !!(val & S2MU005_FLED_FLASH_STATUS(led->channel)); 184 185 unlock: 186 mutex_unlock(&led->lock); 187 188 return ret; 189 } 190 191 static const struct led_flash_ops s2mu005_fled_flash_ops = { 192 .flash_brightness_set = s2m_fled_flash_brightness_set, 193 .timeout_set = s2m_fled_flash_timeout_set, 194 .strobe_set = s2mu005_fled_flash_strobe_set, 195 .strobe_get = s2mu005_fled_flash_strobe_get, 196 }; 197 198 static int s2mu005_fled_init(struct s2m_led *led, struct device *dev, struct regmap *regmap, 199 unsigned int nr_channels) 200 { 201 unsigned int val; 202 int ret; 203 204 ret = regmap_read(regmap, S2MU005_REG_ID, &val); 205 if (ret) 206 return dev_err_probe(dev, ret, "failed to read revision\n"); 207 208 for (int i = 0; i < nr_channels; i++) { 209 /* 210 * Read the revision register. Revision EVT0 has the register 211 * at CTRL4, while EVT1 and higher have it at CTRL6. 212 */ 213 if (FIELD_GET(S2MU005_ID_MASK, val) == 0) 214 led[i].reg_enable = S2MU005_REG_FLED_CTRL4; 215 else 216 led[i].reg_enable = S2MU005_REG_FLED_CTRL6; 217 } 218 219 /* Enable the LED channels. */ 220 ret = regmap_set_bits(regmap, S2MU005_REG_FLED_CTRL1, S2MU005_FLED_CH_EN); 221 if (ret) 222 return dev_err_probe(dev, ret, "failed to enable LED channels\n"); 223 224 return 0; 225 } 226 227 static int s2mu005_fled_init_channel(struct s2m_led *led, struct device *dev, 228 struct fwnode_handle *fwnp) 229 { 230 struct led_classdev *cdev = &led->fled.led_cdev; 231 struct led_init_data init_data = {}; 232 struct v4l2_flash_config v4l2_cfg = {}; 233 int ret; 234 235 cdev->max_brightness = 16; 236 cdev->brightness_set_blocking = s2mu005_fled_torch_brightness_set; 237 cdev->flags |= LED_DEV_CAP_FLASH; 238 239 led->fled.timeout.min = 62000; 240 led->fled.timeout.step = 62000; 241 led->fled.timeout.max = 992000; 242 led->fled.timeout.val = 992000; 243 244 led->fled.brightness.min = 25000; 245 led->fled.brightness.step = 25000; 246 led->fled.brightness.max = 375000; /* 400000 causes flickering */ 247 led->fled.brightness.val = 375000; 248 249 s2m_fled_flash_timeout_set(&led->fled, led->fled.timeout.val); 250 s2m_fled_flash_brightness_set(&led->fled, led->fled.brightness.val); 251 252 led->fled.ops = &s2mu005_fled_flash_ops; 253 254 init_data.fwnode = fwnp; 255 ret = devm_led_classdev_flash_register_ext(dev, &led->fled, &init_data); 256 if (ret) 257 return dev_err_probe(dev, ret, "failed to create LED flash device\n"); 258 259 v4l2_cfg.intensity.min = led->fled.brightness.min; 260 v4l2_cfg.intensity.step = led->fled.brightness.step; 261 v4l2_cfg.intensity.max = led->fled.brightness.max; 262 v4l2_cfg.intensity.val = led->fled.brightness.val; 263 264 v4l2_cfg.has_external_strobe = true; 265 266 led->v4l2_flash = v4l2_flash_init(dev, fwnp, &led->fled, &s2m_fled_v4l2_flash_ops, 267 &v4l2_cfg); 268 if (IS_ERR(led->v4l2_flash)) 269 return dev_err_probe(dev, PTR_ERR(led->v4l2_flash), 270 "failed to create V4L2 flash device\n"); 271 272 ret = devm_add_action_or_reset(dev, s2m_fled_v4l2_flash_release, led->v4l2_flash); 273 if (ret) 274 return dev_err_probe(dev, ret, "failed to add cleanup action\n"); 275 276 return 0; 277 } 278 279 static int s2m_fled_probe(struct platform_device *pdev) 280 { 281 struct device *dev = &pdev->dev; 282 struct sec_pmic_dev *ddata = dev_get_drvdata(dev->parent); 283 struct s2m_led *led; 284 int ret; 285 286 led = devm_kzalloc(dev, sizeof(*led) * MAX_CHANNELS, GFP_KERNEL); 287 if (!led) 288 return -ENOMEM; 289 290 /* Initialize LED controller with variant-specific implementation. */ 291 ret = s2mu005_fled_init(led, dev, ddata->regmap_pmic, MAX_CHANNELS); 292 if (ret) 293 return ret; 294 295 device_for_each_child_node_scoped(dev, child) { 296 u32 reg; 297 298 if (fwnode_property_read_u32(child, "reg", ®)) 299 continue; 300 301 if (reg >= MAX_CHANNELS) { 302 dev_warn(dev, "channel %d is non-existent\n", reg); 303 continue; 304 } 305 306 if (led[reg].regmap) { 307 dev_warn(dev, "duplicate node for channel %d\n", reg); 308 continue; 309 } 310 311 led[reg].regmap = ddata->regmap_pmic; 312 led[reg].channel = (u8)reg; 313 314 ret = devm_mutex_init(dev, &led[reg].lock); 315 if (ret) 316 return dev_err_probe(dev, ret, "failed to create mutex\n"); 317 318 /* Initialize LED channel with variant-specific implementation. */ 319 ret = s2mu005_fled_init_channel(led + reg, dev, child); 320 if (ret) 321 return ret; 322 } 323 324 return 0; 325 } 326 327 static const struct platform_device_id s2m_fled_id_table[] = { 328 { "s2mu005-flash", S2MU005 }, 329 { /* sentinel */ }, 330 }; 331 MODULE_DEVICE_TABLE(platform, s2m_fled_id_table); 332 333 static const struct of_device_id s2m_fled_of_match_table[] = { 334 { .compatible = "samsung,s2mu005-flash", .data = (void *)S2MU005 }, 335 { /* sentinel */ }, 336 }; 337 MODULE_DEVICE_TABLE(of, s2m_fled_of_match_table); 338 339 static struct platform_driver s2m_fled_driver = { 340 .driver = { 341 .name = "s2m-flash", 342 }, 343 .probe = s2m_fled_probe, 344 .id_table = s2m_fled_id_table, 345 }; 346 module_platform_driver(s2m_fled_driver); 347 348 MODULE_DESCRIPTION("Flash/Torch LED Driver for Samsung S2M Series PMICs"); 349 MODULE_AUTHOR("Kaustabh Chakraborty <kauschluss@disroot.org>"); 350 MODULE_LICENSE("GPL"); 351