1caab277bSThomas Gleixner // SPDX-License-Identifier: GPL-2.0-only 2f705806cSGyungoh Yoo /* 3f705806cSGyungoh Yoo * sky81452-backlight.c SKY81452 backlight driver 4f705806cSGyungoh Yoo * 5f705806cSGyungoh Yoo * Copyright 2014 Skyworks Solutions Inc. 6f705806cSGyungoh Yoo * Author : Gyungoh Yoo <jack.yoo@skyworksinc.com> 7f705806cSGyungoh Yoo */ 8f705806cSGyungoh Yoo 9f705806cSGyungoh Yoo #include <linux/backlight.h> 10f705806cSGyungoh Yoo #include <linux/err.h> 11e1915eecSLinus Walleij #include <linux/gpio/consumer.h> 12f705806cSGyungoh Yoo #include <linux/init.h> 13f705806cSGyungoh Yoo #include <linux/kernel.h> 14f705806cSGyungoh Yoo #include <linux/module.h> 15f705806cSGyungoh Yoo #include <linux/of.h> 16f705806cSGyungoh Yoo #include <linux/platform_device.h> 17f705806cSGyungoh Yoo #include <linux/regmap.h> 18f705806cSGyungoh Yoo #include <linux/slab.h> 19f705806cSGyungoh Yoo 20f705806cSGyungoh Yoo /* registers */ 21f705806cSGyungoh Yoo #define SKY81452_REG0 0x00 22f705806cSGyungoh Yoo #define SKY81452_REG1 0x01 23f705806cSGyungoh Yoo #define SKY81452_REG2 0x02 24f705806cSGyungoh Yoo #define SKY81452_REG4 0x04 25f705806cSGyungoh Yoo #define SKY81452_REG5 0x05 26f705806cSGyungoh Yoo 27f705806cSGyungoh Yoo /* bit mask */ 28f705806cSGyungoh Yoo #define SKY81452_CS 0xFF 29f705806cSGyungoh Yoo #define SKY81452_EN 0x3F 30f705806cSGyungoh Yoo #define SKY81452_IGPW 0x20 31f705806cSGyungoh Yoo #define SKY81452_PWMMD 0x10 32f705806cSGyungoh Yoo #define SKY81452_PHASE 0x08 33f705806cSGyungoh Yoo #define SKY81452_ILIM 0x04 34f705806cSGyungoh Yoo #define SKY81452_VSHRT 0x03 35f705806cSGyungoh Yoo #define SKY81452_OCP 0x80 36f705806cSGyungoh Yoo #define SKY81452_OTMP 0x40 37f705806cSGyungoh Yoo #define SKY81452_SHRT 0x3F 38f705806cSGyungoh Yoo #define SKY81452_OPN 0x3F 39f705806cSGyungoh Yoo 40f705806cSGyungoh Yoo #define SKY81452_DEFAULT_NAME "lcd-backlight" 41f705806cSGyungoh Yoo #define SKY81452_MAX_BRIGHTNESS (SKY81452_CS + 1) 42f705806cSGyungoh Yoo 4308bf73a6SLinus Walleij /** 4408bf73a6SLinus Walleij * struct sky81452_platform_data 4508bf73a6SLinus Walleij * @name: backlight driver name. 46c847e429SLee Jones * If it is not defined, default name is lcd-backlight. 47c847e429SLee Jones * @gpiod_enable:GPIO descriptor which control EN pin 4808bf73a6SLinus Walleij * @enable: Enable mask for current sink channel 1, 2, 3, 4, 5 and 6. 4908bf73a6SLinus Walleij * @ignore_pwm: true if DPWMI should be ignored. 5008bf73a6SLinus Walleij * @dpwm_mode: true is DPWM dimming mode, otherwise Analog dimming mode. 5108bf73a6SLinus Walleij * @phase_shift:true is phase shift mode. 52c847e429SLee Jones * @short_detection_threshold: It should be one of 4, 5, 6 and 7V. 5308bf73a6SLinus Walleij * @boost_current_limit: It should be one of 2300, 2750mA. 5408bf73a6SLinus Walleij */ 5508bf73a6SLinus Walleij struct sky81452_bl_platform_data { 5608bf73a6SLinus Walleij const char *name; 5708bf73a6SLinus Walleij struct gpio_desc *gpiod_enable; 5808bf73a6SLinus Walleij unsigned int enable; 5908bf73a6SLinus Walleij bool ignore_pwm; 6008bf73a6SLinus Walleij bool dpwm_mode; 6108bf73a6SLinus Walleij bool phase_shift; 6208bf73a6SLinus Walleij unsigned int short_detection_threshold; 6308bf73a6SLinus Walleij unsigned int boost_current_limit; 6408bf73a6SLinus Walleij }; 6508bf73a6SLinus Walleij 66f705806cSGyungoh Yoo #define CTZ(b) __builtin_ctz(b) 67f705806cSGyungoh Yoo 68f705806cSGyungoh Yoo static int sky81452_bl_update_status(struct backlight_device *bd) 69f705806cSGyungoh Yoo { 70f705806cSGyungoh Yoo const struct sky81452_bl_platform_data *pdata = 71f705806cSGyungoh Yoo dev_get_platdata(bd->dev.parent); 72f705806cSGyungoh Yoo const unsigned int brightness = (unsigned int)bd->props.brightness; 73f705806cSGyungoh Yoo struct regmap *regmap = bl_get_data(bd); 74f705806cSGyungoh Yoo int ret; 75f705806cSGyungoh Yoo 76f705806cSGyungoh Yoo if (brightness > 0) { 77f705806cSGyungoh Yoo ret = regmap_write(regmap, SKY81452_REG0, brightness - 1); 78047ffbb2SAxel Lin if (ret < 0) 79f705806cSGyungoh Yoo return ret; 80f705806cSGyungoh Yoo 81f705806cSGyungoh Yoo return regmap_update_bits(regmap, SKY81452_REG1, SKY81452_EN, 82f705806cSGyungoh Yoo pdata->enable << CTZ(SKY81452_EN)); 83f705806cSGyungoh Yoo } 84f705806cSGyungoh Yoo 85f705806cSGyungoh Yoo return regmap_update_bits(regmap, SKY81452_REG1, SKY81452_EN, 0); 86f705806cSGyungoh Yoo } 87f705806cSGyungoh Yoo 88f705806cSGyungoh Yoo static const struct backlight_ops sky81452_bl_ops = { 89f705806cSGyungoh Yoo .update_status = sky81452_bl_update_status, 90f705806cSGyungoh Yoo }; 91f705806cSGyungoh Yoo 92f705806cSGyungoh Yoo static ssize_t sky81452_bl_store_enable(struct device *dev, 93f705806cSGyungoh Yoo struct device_attribute *attr, const char *buf, size_t count) 94f705806cSGyungoh Yoo { 95f705806cSGyungoh Yoo struct regmap *regmap = bl_get_data(to_backlight_device(dev)); 96f705806cSGyungoh Yoo unsigned long value; 97f705806cSGyungoh Yoo int ret; 98f705806cSGyungoh Yoo 99f705806cSGyungoh Yoo ret = kstrtoul(buf, 16, &value); 100047ffbb2SAxel Lin if (ret < 0) 101f705806cSGyungoh Yoo return ret; 102f705806cSGyungoh Yoo 103f705806cSGyungoh Yoo ret = regmap_update_bits(regmap, SKY81452_REG1, SKY81452_EN, 104f705806cSGyungoh Yoo value << CTZ(SKY81452_EN)); 105047ffbb2SAxel Lin if (ret < 0) 106f705806cSGyungoh Yoo return ret; 107f705806cSGyungoh Yoo 108f705806cSGyungoh Yoo return count; 109f705806cSGyungoh Yoo } 110f705806cSGyungoh Yoo 111f705806cSGyungoh Yoo static ssize_t sky81452_bl_show_open_short(struct device *dev, 112f705806cSGyungoh Yoo struct device_attribute *attr, char *buf) 113f705806cSGyungoh Yoo { 114f705806cSGyungoh Yoo struct regmap *regmap = bl_get_data(to_backlight_device(dev)); 115f705806cSGyungoh Yoo unsigned int reg, value = 0; 116f705806cSGyungoh Yoo char tmp[3]; 117f705806cSGyungoh Yoo int i, ret; 118f705806cSGyungoh Yoo 119f705806cSGyungoh Yoo reg = !strcmp(attr->attr.name, "open") ? SKY81452_REG5 : SKY81452_REG4; 120f705806cSGyungoh Yoo ret = regmap_read(regmap, reg, &value); 121047ffbb2SAxel Lin if (ret < 0) 122f705806cSGyungoh Yoo return ret; 123f705806cSGyungoh Yoo 124f705806cSGyungoh Yoo if (value & SKY81452_SHRT) { 125f705806cSGyungoh Yoo *buf = 0; 126f705806cSGyungoh Yoo for (i = 0; i < 6; i++) { 127f705806cSGyungoh Yoo if (value & 0x01) { 128f705806cSGyungoh Yoo sprintf(tmp, "%d ", i + 1); 129f705806cSGyungoh Yoo strcat(buf, tmp); 130f705806cSGyungoh Yoo } 131f705806cSGyungoh Yoo value >>= 1; 132f705806cSGyungoh Yoo } 133f705806cSGyungoh Yoo strcat(buf, "\n"); 134f705806cSGyungoh Yoo } else { 135f705806cSGyungoh Yoo strcpy(buf, "none\n"); 136f705806cSGyungoh Yoo } 137f705806cSGyungoh Yoo 138f705806cSGyungoh Yoo return strlen(buf); 139f705806cSGyungoh Yoo } 140f705806cSGyungoh Yoo 141f705806cSGyungoh Yoo static ssize_t sky81452_bl_show_fault(struct device *dev, 142f705806cSGyungoh Yoo struct device_attribute *attr, char *buf) 143f705806cSGyungoh Yoo { 144f705806cSGyungoh Yoo struct regmap *regmap = bl_get_data(to_backlight_device(dev)); 145f705806cSGyungoh Yoo unsigned int value = 0; 146f705806cSGyungoh Yoo int ret; 147f705806cSGyungoh Yoo 148f705806cSGyungoh Yoo ret = regmap_read(regmap, SKY81452_REG4, &value); 149047ffbb2SAxel Lin if (ret < 0) 150f705806cSGyungoh Yoo return ret; 151f705806cSGyungoh Yoo 152f705806cSGyungoh Yoo *buf = 0; 153f705806cSGyungoh Yoo 154f705806cSGyungoh Yoo if (value & SKY81452_OCP) 155f705806cSGyungoh Yoo strcat(buf, "over-current "); 156f705806cSGyungoh Yoo 157f705806cSGyungoh Yoo if (value & SKY81452_OTMP) 158f705806cSGyungoh Yoo strcat(buf, "over-temperature"); 159f705806cSGyungoh Yoo 160f705806cSGyungoh Yoo strcat(buf, "\n"); 161f705806cSGyungoh Yoo return strlen(buf); 162f705806cSGyungoh Yoo } 163f705806cSGyungoh Yoo 164f705806cSGyungoh Yoo static DEVICE_ATTR(enable, S_IWGRP | S_IWUSR, NULL, sky81452_bl_store_enable); 165f705806cSGyungoh Yoo static DEVICE_ATTR(open, S_IRUGO, sky81452_bl_show_open_short, NULL); 166f705806cSGyungoh Yoo static DEVICE_ATTR(short, S_IRUGO, sky81452_bl_show_open_short, NULL); 167f705806cSGyungoh Yoo static DEVICE_ATTR(fault, S_IRUGO, sky81452_bl_show_fault, NULL); 168f705806cSGyungoh Yoo 169f705806cSGyungoh Yoo static struct attribute *sky81452_bl_attribute[] = { 170f705806cSGyungoh Yoo &dev_attr_enable.attr, 171f705806cSGyungoh Yoo &dev_attr_open.attr, 172f705806cSGyungoh Yoo &dev_attr_short.attr, 173f705806cSGyungoh Yoo &dev_attr_fault.attr, 174f705806cSGyungoh Yoo NULL 175f705806cSGyungoh Yoo }; 176f705806cSGyungoh Yoo 177f705806cSGyungoh Yoo static const struct attribute_group sky81452_bl_attr_group = { 178f705806cSGyungoh Yoo .attrs = sky81452_bl_attribute, 179f705806cSGyungoh Yoo }; 180f705806cSGyungoh Yoo 181f705806cSGyungoh Yoo #ifdef CONFIG_OF 182f705806cSGyungoh Yoo static struct sky81452_bl_platform_data *sky81452_bl_parse_dt( 183f705806cSGyungoh Yoo struct device *dev) 184f705806cSGyungoh Yoo { 185f705806cSGyungoh Yoo struct device_node *np = of_node_get(dev->of_node); 186f705806cSGyungoh Yoo struct sky81452_bl_platform_data *pdata; 187f705806cSGyungoh Yoo int num_entry; 188f705806cSGyungoh Yoo unsigned int sources[6]; 189f705806cSGyungoh Yoo int ret; 190f705806cSGyungoh Yoo 191f705806cSGyungoh Yoo if (!np) { 192f705806cSGyungoh Yoo dev_err(dev, "backlight node not found.\n"); 193f705806cSGyungoh Yoo return ERR_PTR(-ENODATA); 194f705806cSGyungoh Yoo } 195f705806cSGyungoh Yoo 196f705806cSGyungoh Yoo pdata = devm_kzalloc(dev, sizeof(*pdata), GFP_KERNEL); 197f705806cSGyungoh Yoo if (!pdata) { 198f705806cSGyungoh Yoo of_node_put(np); 199f705806cSGyungoh Yoo return ERR_PTR(-ENOMEM); 200f705806cSGyungoh Yoo } 201f705806cSGyungoh Yoo 202f705806cSGyungoh Yoo of_property_read_string(np, "name", &pdata->name); 203f705806cSGyungoh Yoo pdata->ignore_pwm = of_property_read_bool(np, "skyworks,ignore-pwm"); 204f705806cSGyungoh Yoo pdata->dpwm_mode = of_property_read_bool(np, "skyworks,dpwm-mode"); 205f705806cSGyungoh Yoo pdata->phase_shift = of_property_read_bool(np, "skyworks,phase-shift"); 206e1915eecSLinus Walleij pdata->gpiod_enable = devm_gpiod_get_optional(dev, NULL, GPIOD_OUT_HIGH); 207f705806cSGyungoh Yoo 208f705806cSGyungoh Yoo ret = of_property_count_u32_elems(np, "led-sources"); 209047ffbb2SAxel Lin if (ret < 0) { 210f705806cSGyungoh Yoo pdata->enable = SKY81452_EN >> CTZ(SKY81452_EN); 211f705806cSGyungoh Yoo } else { 212f705806cSGyungoh Yoo num_entry = ret; 213f705806cSGyungoh Yoo if (num_entry > 6) 214f705806cSGyungoh Yoo num_entry = 6; 215f705806cSGyungoh Yoo 216f705806cSGyungoh Yoo ret = of_property_read_u32_array(np, "led-sources", sources, 217f705806cSGyungoh Yoo num_entry); 218047ffbb2SAxel Lin if (ret < 0) { 219f705806cSGyungoh Yoo dev_err(dev, "led-sources node is invalid.\n"); 220b7a4f80bSdinghao.liu@zju.edu.cn of_node_put(np); 221f705806cSGyungoh Yoo return ERR_PTR(-EINVAL); 222f705806cSGyungoh Yoo } 223f705806cSGyungoh Yoo 224f705806cSGyungoh Yoo pdata->enable = 0; 225f705806cSGyungoh Yoo while (--num_entry) 226f705806cSGyungoh Yoo pdata->enable |= (1 << sources[num_entry]); 227f705806cSGyungoh Yoo } 228f705806cSGyungoh Yoo 229f705806cSGyungoh Yoo ret = of_property_read_u32(np, 230f705806cSGyungoh Yoo "skyworks,short-detection-threshold-volt", 231f705806cSGyungoh Yoo &pdata->short_detection_threshold); 232047ffbb2SAxel Lin if (ret < 0) 233f705806cSGyungoh Yoo pdata->short_detection_threshold = 7; 234f705806cSGyungoh Yoo 235f705806cSGyungoh Yoo ret = of_property_read_u32(np, "skyworks,current-limit-mA", 236f705806cSGyungoh Yoo &pdata->boost_current_limit); 237047ffbb2SAxel Lin if (ret < 0) 238f705806cSGyungoh Yoo pdata->boost_current_limit = 2750; 239f705806cSGyungoh Yoo 240f705806cSGyungoh Yoo of_node_put(np); 241f705806cSGyungoh Yoo return pdata; 242f705806cSGyungoh Yoo } 243f705806cSGyungoh Yoo #else 244f705806cSGyungoh Yoo static struct sky81452_bl_platform_data *sky81452_bl_parse_dt( 245f705806cSGyungoh Yoo struct device *dev) 246f705806cSGyungoh Yoo { 247f705806cSGyungoh Yoo return ERR_PTR(-EINVAL); 248f705806cSGyungoh Yoo } 249f705806cSGyungoh Yoo #endif 250f705806cSGyungoh Yoo 251f705806cSGyungoh Yoo static int sky81452_bl_init_device(struct regmap *regmap, 252f705806cSGyungoh Yoo struct sky81452_bl_platform_data *pdata) 253f705806cSGyungoh Yoo { 254f705806cSGyungoh Yoo unsigned int value; 255f705806cSGyungoh Yoo 256f705806cSGyungoh Yoo value = pdata->ignore_pwm ? SKY81452_IGPW : 0; 257f705806cSGyungoh Yoo value |= pdata->dpwm_mode ? SKY81452_PWMMD : 0; 258f705806cSGyungoh Yoo value |= pdata->phase_shift ? 0 : SKY81452_PHASE; 259f705806cSGyungoh Yoo 260f705806cSGyungoh Yoo if (pdata->boost_current_limit == 2300) 261f705806cSGyungoh Yoo value |= SKY81452_ILIM; 262f705806cSGyungoh Yoo else if (pdata->boost_current_limit != 2750) 263f705806cSGyungoh Yoo return -EINVAL; 264f705806cSGyungoh Yoo 265f705806cSGyungoh Yoo if (pdata->short_detection_threshold < 4 || 266f705806cSGyungoh Yoo pdata->short_detection_threshold > 7) 267f705806cSGyungoh Yoo return -EINVAL; 268f705806cSGyungoh Yoo value |= (7 - pdata->short_detection_threshold) << CTZ(SKY81452_VSHRT); 269f705806cSGyungoh Yoo 270f705806cSGyungoh Yoo return regmap_write(regmap, SKY81452_REG2, value); 271f705806cSGyungoh Yoo } 272f705806cSGyungoh Yoo 273f705806cSGyungoh Yoo static int sky81452_bl_probe(struct platform_device *pdev) 274f705806cSGyungoh Yoo { 275f705806cSGyungoh Yoo struct device *dev = &pdev->dev; 276f705806cSGyungoh Yoo struct regmap *regmap = dev_get_drvdata(dev->parent); 27708bf73a6SLinus Walleij struct sky81452_bl_platform_data *pdata; 278f705806cSGyungoh Yoo struct backlight_device *bd; 279f705806cSGyungoh Yoo struct backlight_properties props; 280f705806cSGyungoh Yoo const char *name; 281f705806cSGyungoh Yoo int ret; 282f705806cSGyungoh Yoo 283f705806cSGyungoh Yoo pdata = sky81452_bl_parse_dt(dev); 284f705806cSGyungoh Yoo if (IS_ERR(pdata)) 285f705806cSGyungoh Yoo return PTR_ERR(pdata); 286f705806cSGyungoh Yoo 287f705806cSGyungoh Yoo ret = sky81452_bl_init_device(regmap, pdata); 288047ffbb2SAxel Lin if (ret < 0) { 289f705806cSGyungoh Yoo dev_err(dev, "failed to initialize. err=%d\n", ret); 290f705806cSGyungoh Yoo return ret; 291f705806cSGyungoh Yoo } 292f705806cSGyungoh Yoo 293f705806cSGyungoh Yoo memset(&props, 0, sizeof(props)); 294*4a98e5efSZheng Yongjun props.max_brightness = SKY81452_MAX_BRIGHTNESS; 295f705806cSGyungoh Yoo name = pdata->name ? pdata->name : SKY81452_DEFAULT_NAME; 296f705806cSGyungoh Yoo bd = devm_backlight_device_register(dev, name, dev, regmap, 297f705806cSGyungoh Yoo &sky81452_bl_ops, &props); 298f705806cSGyungoh Yoo if (IS_ERR(bd)) { 299f705806cSGyungoh Yoo dev_err(dev, "failed to register. err=%ld\n", PTR_ERR(bd)); 300f705806cSGyungoh Yoo return PTR_ERR(bd); 301f705806cSGyungoh Yoo } 302f705806cSGyungoh Yoo 303f705806cSGyungoh Yoo platform_set_drvdata(pdev, bd); 304f705806cSGyungoh Yoo 305f705806cSGyungoh Yoo ret = sysfs_create_group(&bd->dev.kobj, &sky81452_bl_attr_group); 306047ffbb2SAxel Lin if (ret < 0) { 307f705806cSGyungoh Yoo dev_err(dev, "failed to create attribute. err=%d\n", ret); 308f705806cSGyungoh Yoo return ret; 309f705806cSGyungoh Yoo } 310f705806cSGyungoh Yoo 311f705806cSGyungoh Yoo return ret; 312f705806cSGyungoh Yoo } 313f705806cSGyungoh Yoo 314f705806cSGyungoh Yoo static int sky81452_bl_remove(struct platform_device *pdev) 315f705806cSGyungoh Yoo { 316f705806cSGyungoh Yoo const struct sky81452_bl_platform_data *pdata = 317f705806cSGyungoh Yoo dev_get_platdata(&pdev->dev); 318f705806cSGyungoh Yoo struct backlight_device *bd = platform_get_drvdata(pdev); 319f705806cSGyungoh Yoo 320f705806cSGyungoh Yoo sysfs_remove_group(&bd->dev.kobj, &sky81452_bl_attr_group); 321f705806cSGyungoh Yoo 322f705806cSGyungoh Yoo bd->props.power = FB_BLANK_UNBLANK; 323f705806cSGyungoh Yoo bd->props.brightness = 0; 324f705806cSGyungoh Yoo backlight_update_status(bd); 325f705806cSGyungoh Yoo 326e1915eecSLinus Walleij if (pdata->gpiod_enable) 327e1915eecSLinus Walleij gpiod_set_value_cansleep(pdata->gpiod_enable, 0); 328f705806cSGyungoh Yoo 329f705806cSGyungoh Yoo return 0; 330f705806cSGyungoh Yoo } 331f705806cSGyungoh Yoo 332f705806cSGyungoh Yoo #ifdef CONFIG_OF 333f705806cSGyungoh Yoo static const struct of_device_id sky81452_bl_of_match[] = { 334f705806cSGyungoh Yoo { .compatible = "skyworks,sky81452-backlight", }, 335f705806cSGyungoh Yoo { } 336f705806cSGyungoh Yoo }; 337f705806cSGyungoh Yoo MODULE_DEVICE_TABLE(of, sky81452_bl_of_match); 338f705806cSGyungoh Yoo #endif 339f705806cSGyungoh Yoo 340f705806cSGyungoh Yoo static struct platform_driver sky81452_bl_driver = { 341f705806cSGyungoh Yoo .driver = { 342f705806cSGyungoh Yoo .name = "sky81452-backlight", 343f705806cSGyungoh Yoo .of_match_table = of_match_ptr(sky81452_bl_of_match), 344f705806cSGyungoh Yoo }, 345f705806cSGyungoh Yoo .probe = sky81452_bl_probe, 346f705806cSGyungoh Yoo .remove = sky81452_bl_remove, 347f705806cSGyungoh Yoo }; 348f705806cSGyungoh Yoo 349f705806cSGyungoh Yoo module_platform_driver(sky81452_bl_driver); 350f705806cSGyungoh Yoo 351f705806cSGyungoh Yoo MODULE_DESCRIPTION("Skyworks SKY81452 backlight driver"); 352f705806cSGyungoh Yoo MODULE_AUTHOR("Gyungoh Yoo <jack.yoo@skyworksinc.com>"); 353f705806cSGyungoh Yoo MODULE_LICENSE("GPL v2"); 354