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