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