1/* Copyright (c) 2015, Sony Mobile Communications, AB. 2 * 3 * This program is free software; you can redistribute it and/or modify 4 * it under the terms of the GNU General Public License version 2 and 5 * only version 2 as published by the Free Software Foundation. 6 * 7 * This program is distributed in the hope that it will be useful, 8 * but WITHOUT ANY WARRANTY; without even the implied warranty of 9 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 10 * GNU General Public License for more details. 11 */ 12 13#include <linux/kernel.h> 14#include <linux/leds.h> 15#include <linux/module.h> 16#include <linux/of.h> 17#include <linux/of_device.h> 18#include <linux/regmap.h> 19 20#define PM8941_WLED_REG_VAL_BASE 0x40 21#define PM8941_WLED_REG_VAL_MAX 0xFFF 22 23#define PM8941_WLED_REG_MOD_EN 0x46 24#define PM8941_WLED_REG_MOD_EN_BIT BIT(7) 25#define PM8941_WLED_REG_MOD_EN_MASK BIT(7) 26 27#define PM8941_WLED_REG_SYNC 0x47 28#define PM8941_WLED_REG_SYNC_MASK 0x07 29#define PM8941_WLED_REG_SYNC_LED1 BIT(0) 30#define PM8941_WLED_REG_SYNC_LED2 BIT(1) 31#define PM8941_WLED_REG_SYNC_LED3 BIT(2) 32#define PM8941_WLED_REG_SYNC_ALL 0x07 33#define PM8941_WLED_REG_SYNC_CLEAR 0x00 34 35#define PM8941_WLED_REG_FREQ 0x4c 36#define PM8941_WLED_REG_FREQ_MASK 0x0f 37 38#define PM8941_WLED_REG_OVP 0x4d 39#define PM8941_WLED_REG_OVP_MASK 0x03 40 41#define PM8941_WLED_REG_BOOST 0x4e 42#define PM8941_WLED_REG_BOOST_MASK 0x07 43 44#define PM8941_WLED_REG_SINK 0x4f 45#define PM8941_WLED_REG_SINK_MASK 0xe0 46#define PM8941_WLED_REG_SINK_SHFT 0x05 47 48/* Per-'string' registers below */ 49#define PM8941_WLED_REG_STR_OFFSET 0x10 50 51#define PM8941_WLED_REG_STR_MOD_EN_BASE 0x60 52#define PM8941_WLED_REG_STR_MOD_MASK BIT(7) 53#define PM8941_WLED_REG_STR_MOD_EN BIT(7) 54 55#define PM8941_WLED_REG_STR_SCALE_BASE 0x62 56#define PM8941_WLED_REG_STR_SCALE_MASK 0x1f 57 58#define PM8941_WLED_REG_STR_MOD_SRC_BASE 0x63 59#define PM8941_WLED_REG_STR_MOD_SRC_MASK 0x01 60#define PM8941_WLED_REG_STR_MOD_SRC_INT 0x00 61#define PM8941_WLED_REG_STR_MOD_SRC_EXT 0x01 62 63#define PM8941_WLED_REG_STR_CABC_BASE 0x66 64#define PM8941_WLED_REG_STR_CABC_MASK BIT(7) 65#define PM8941_WLED_REG_STR_CABC_EN BIT(7) 66 67struct pm8941_wled_config { 68 u32 i_boost_limit; 69 u32 ovp; 70 u32 switch_freq; 71 u32 num_strings; 72 u32 i_limit; 73 bool cs_out_en; 74 bool ext_gen; 75 bool cabc_en; 76}; 77 78struct pm8941_wled { 79 struct regmap *regmap; 80 u16 addr; 81 82 struct led_classdev cdev; 83 84 struct pm8941_wled_config cfg; 85}; 86 87static int pm8941_wled_set(struct led_classdev *cdev, 88 enum led_brightness value) 89{ 90 struct pm8941_wled *wled; 91 u8 ctrl = 0; 92 u16 val; 93 int rc; 94 int i; 95 96 wled = container_of(cdev, struct pm8941_wled, cdev); 97 98 if (value != 0) 99 ctrl = PM8941_WLED_REG_MOD_EN_BIT; 100 101 val = value * PM8941_WLED_REG_VAL_MAX / LED_FULL; 102 103 rc = regmap_update_bits(wled->regmap, 104 wled->addr + PM8941_WLED_REG_MOD_EN, 105 PM8941_WLED_REG_MOD_EN_MASK, ctrl); 106 if (rc) 107 return rc; 108 109 for (i = 0; i < wled->cfg.num_strings; ++i) { 110 u8 v[2] = { val & 0xff, (val >> 8) & 0xf }; 111 112 rc = regmap_bulk_write(wled->regmap, 113 wled->addr + PM8941_WLED_REG_VAL_BASE + 2 * i, 114 v, 2); 115 if (rc) 116 return rc; 117 } 118 119 rc = regmap_update_bits(wled->regmap, 120 wled->addr + PM8941_WLED_REG_SYNC, 121 PM8941_WLED_REG_SYNC_MASK, PM8941_WLED_REG_SYNC_ALL); 122 if (rc) 123 return rc; 124 125 rc = regmap_update_bits(wled->regmap, 126 wled->addr + PM8941_WLED_REG_SYNC, 127 PM8941_WLED_REG_SYNC_MASK, PM8941_WLED_REG_SYNC_CLEAR); 128 return rc; 129} 130 131static void pm8941_wled_set_brightness(struct led_classdev *cdev, 132 enum led_brightness value) 133{ 134 if (pm8941_wled_set(cdev, value)) { 135 dev_err(cdev->dev, "Unable to set brightness\n"); 136 return; 137 } 138 cdev->brightness = value; 139} 140 141static int pm8941_wled_setup(struct pm8941_wled *wled) 142{ 143 int rc; 144 int i; 145 146 rc = regmap_update_bits(wled->regmap, 147 wled->addr + PM8941_WLED_REG_OVP, 148 PM8941_WLED_REG_OVP_MASK, wled->cfg.ovp); 149 if (rc) 150 return rc; 151 152 rc = regmap_update_bits(wled->regmap, 153 wled->addr + PM8941_WLED_REG_BOOST, 154 PM8941_WLED_REG_BOOST_MASK, wled->cfg.i_boost_limit); 155 if (rc) 156 return rc; 157 158 rc = regmap_update_bits(wled->regmap, 159 wled->addr + PM8941_WLED_REG_FREQ, 160 PM8941_WLED_REG_FREQ_MASK, wled->cfg.switch_freq); 161 if (rc) 162 return rc; 163 164 if (wled->cfg.cs_out_en) { 165 u8 all = (BIT(wled->cfg.num_strings) - 1) 166 << PM8941_WLED_REG_SINK_SHFT; 167 168 rc = regmap_update_bits(wled->regmap, 169 wled->addr + PM8941_WLED_REG_SINK, 170 PM8941_WLED_REG_SINK_MASK, all); 171 if (rc) 172 return rc; 173 } 174 175 for (i = 0; i < wled->cfg.num_strings; ++i) { 176 u16 addr = wled->addr + PM8941_WLED_REG_STR_OFFSET * i; 177 178 rc = regmap_update_bits(wled->regmap, 179 addr + PM8941_WLED_REG_STR_MOD_EN_BASE, 180 PM8941_WLED_REG_STR_MOD_MASK, 181 PM8941_WLED_REG_STR_MOD_EN); 182 if (rc) 183 return rc; 184 185 if (wled->cfg.ext_gen) { 186 rc = regmap_update_bits(wled->regmap, 187 addr + PM8941_WLED_REG_STR_MOD_SRC_BASE, 188 PM8941_WLED_REG_STR_MOD_SRC_MASK, 189 PM8941_WLED_REG_STR_MOD_SRC_EXT); 190 if (rc) 191 return rc; 192 } 193 194 rc = regmap_update_bits(wled->regmap, 195 addr + PM8941_WLED_REG_STR_SCALE_BASE, 196 PM8941_WLED_REG_STR_SCALE_MASK, 197 wled->cfg.i_limit); 198 if (rc) 199 return rc; 200 201 rc = regmap_update_bits(wled->regmap, 202 addr + PM8941_WLED_REG_STR_CABC_BASE, 203 PM8941_WLED_REG_STR_CABC_MASK, 204 wled->cfg.cabc_en ? 205 PM8941_WLED_REG_STR_CABC_EN : 0); 206 if (rc) 207 return rc; 208 } 209 210 return 0; 211} 212 213static const struct pm8941_wled_config pm8941_wled_config_defaults = { 214 .i_boost_limit = 3, 215 .i_limit = 20, 216 .ovp = 2, 217 .switch_freq = 5, 218 .num_strings = 0, 219 .cs_out_en = false, 220 .ext_gen = false, 221 .cabc_en = false, 222}; 223 224struct pm8941_wled_var_cfg { 225 const u32 *values; 226 u32 (*fn)(u32); 227 int size; 228}; 229 230static const u32 pm8941_wled_i_boost_limit_values[] = { 231 105, 385, 525, 805, 980, 1260, 1400, 1680, 232}; 233 234static const struct pm8941_wled_var_cfg pm8941_wled_i_boost_limit_cfg = { 235 .values = pm8941_wled_i_boost_limit_values, 236 .size = ARRAY_SIZE(pm8941_wled_i_boost_limit_values), 237}; 238 239static const u32 pm8941_wled_ovp_values[] = { 240 35, 32, 29, 27, 241}; 242 243static const struct pm8941_wled_var_cfg pm8941_wled_ovp_cfg = { 244 .values = pm8941_wled_ovp_values, 245 .size = ARRAY_SIZE(pm8941_wled_ovp_values), 246}; 247 248static u32 pm8941_wled_num_strings_values_fn(u32 idx) 249{ 250 return idx + 1; 251} 252 253static const struct pm8941_wled_var_cfg pm8941_wled_num_strings_cfg = { 254 .fn = pm8941_wled_num_strings_values_fn, 255 .size = 3, 256}; 257 258static u32 pm8941_wled_switch_freq_values_fn(u32 idx) 259{ 260 return 19200 / (2 * (1 + idx)); 261} 262 263static const struct pm8941_wled_var_cfg pm8941_wled_switch_freq_cfg = { 264 .fn = pm8941_wled_switch_freq_values_fn, 265 .size = 16, 266}; 267 268static const struct pm8941_wled_var_cfg pm8941_wled_i_limit_cfg = { 269 .size = 26, 270}; 271 272static u32 pm8941_wled_values(const struct pm8941_wled_var_cfg *cfg, u32 idx) 273{ 274 if (idx >= cfg->size) 275 return UINT_MAX; 276 if (cfg->fn) 277 return cfg->fn(idx); 278 if (cfg->values) 279 return cfg->values[idx]; 280 return idx; 281} 282 283static int pm8941_wled_configure(struct pm8941_wled *wled, struct device *dev) 284{ 285 struct pm8941_wled_config *cfg = &wled->cfg; 286 u32 val; 287 int rc; 288 u32 c; 289 int i; 290 int j; 291 292 const struct { 293 const char *name; 294 u32 *val_ptr; 295 const struct pm8941_wled_var_cfg *cfg; 296 } u32_opts[] = { 297 { 298 "qcom,current-boost-limit", 299 &cfg->i_boost_limit, 300 .cfg = &pm8941_wled_i_boost_limit_cfg, 301 }, 302 { 303 "qcom,current-limit", 304 &cfg->i_limit, 305 .cfg = &pm8941_wled_i_limit_cfg, 306 }, 307 { 308 "qcom,ovp", 309 &cfg->ovp, 310 .cfg = &pm8941_wled_ovp_cfg, 311 }, 312 { 313 "qcom,switching-freq", 314 &cfg->switch_freq, 315 .cfg = &pm8941_wled_switch_freq_cfg, 316 }, 317 { 318 "qcom,num-strings", 319 &cfg->num_strings, 320 .cfg = &pm8941_wled_num_strings_cfg, 321 }, 322 }; 323 const struct { 324 const char *name; 325 bool *val_ptr; 326 } bool_opts[] = { 327 { "qcom,cs-out", &cfg->cs_out_en, }, 328 { "qcom,ext-gen", &cfg->ext_gen, }, 329 { "qcom,cabc", &cfg->cabc_en, }, 330 }; 331 332 rc = of_property_read_u32(dev->of_node, "reg", &val); 333 if (rc || val > 0xffff) { 334 dev_err(dev, "invalid IO resources\n"); 335 return rc ? rc : -EINVAL; 336 } 337 wled->addr = val; 338 339 rc = of_property_read_string(dev->of_node, "label", &wled->cdev.name); 340 if (rc) 341 wled->cdev.name = dev->of_node->name; 342 343 wled->cdev.default_trigger = of_get_property(dev->of_node, 344 "linux,default-trigger", NULL); 345 346 *cfg = pm8941_wled_config_defaults; 347 for (i = 0; i < ARRAY_SIZE(u32_opts); ++i) { 348 rc = of_property_read_u32(dev->of_node, u32_opts[i].name, &val); 349 if (rc == -EINVAL) { 350 continue; 351 } else if (rc) { 352 dev_err(dev, "error reading '%s'\n", u32_opts[i].name); 353 return rc; 354 } 355 356 c = UINT_MAX; 357 for (j = 0; c != val; j++) { 358 c = pm8941_wled_values(u32_opts[i].cfg, j); 359 if (c == UINT_MAX) { 360 dev_err(dev, "invalid value for '%s'\n", 361 u32_opts[i].name); 362 return -EINVAL; 363 } 364 } 365 366 dev_dbg(dev, "'%s' = %u\n", u32_opts[i].name, c); 367 *u32_opts[i].val_ptr = j; 368 } 369 370 for (i = 0; i < ARRAY_SIZE(bool_opts); ++i) { 371 if (of_property_read_bool(dev->of_node, bool_opts[i].name)) 372 *bool_opts[i].val_ptr = true; 373 } 374 375 cfg->num_strings = cfg->num_strings + 1; 376 377 return 0; 378} 379 380static int pm8941_wled_probe(struct platform_device *pdev) 381{ 382 struct pm8941_wled *wled; 383 struct regmap *regmap; 384 int rc; 385 386 regmap = dev_get_regmap(pdev->dev.parent, NULL); 387 if (!regmap) { 388 dev_err(&pdev->dev, "Unable to get regmap\n"); 389 return -EINVAL; 390 } 391 392 wled = devm_kzalloc(&pdev->dev, sizeof(*wled), GFP_KERNEL); 393 if (!wled) 394 return -ENOMEM; 395 396 wled->regmap = regmap; 397 398 rc = pm8941_wled_configure(wled, &pdev->dev); 399 if (rc) 400 return rc; 401 402 rc = pm8941_wled_setup(wled); 403 if (rc) 404 return rc; 405 406 wled->cdev.brightness_set = pm8941_wled_set_brightness; 407 408 rc = devm_led_classdev_register(&pdev->dev, &wled->cdev); 409 if (rc) 410 return rc; 411 412 platform_set_drvdata(pdev, wled); 413 414 return 0; 415}; 416 417static const struct of_device_id pm8941_wled_match_table[] = { 418 { .compatible = "qcom,pm8941-wled" }, 419 {} 420}; 421MODULE_DEVICE_TABLE(of, pm8941_wled_match_table); 422 423static struct platform_driver pm8941_wled_driver = { 424 .probe = pm8941_wled_probe, 425 .driver = { 426 .name = "pm8941-wled", 427 .of_match_table = pm8941_wled_match_table, 428 }, 429}; 430 431module_platform_driver(pm8941_wled_driver); 432 433MODULE_DESCRIPTION("pm8941 wled driver"); 434MODULE_LICENSE("GPL v2"); 435MODULE_ALIAS("platform:pm8941-wled"); 436