1 // SPDX-License-Identifier: GPL-2.0-only 2 /* 3 * leds-max8997.c - LED class driver for MAX8997 LEDs. 4 * 5 * Copyright (C) 2011 Samsung Electronics 6 * Donggeun Kim <dg77.kim@samsung.com> 7 */ 8 9 #include <linux/module.h> 10 #include <linux/err.h> 11 #include <linux/slab.h> 12 #include <linux/leds.h> 13 #include <linux/mfd/max8997.h> 14 #include <linux/mfd/max8997-private.h> 15 #include <linux/platform_device.h> 16 17 #define MAX8997_LED_FLASH_SHIFT 3 18 #define MAX8997_LED_FLASH_CUR_MASK 0xf8 19 #define MAX8997_LED_MOVIE_SHIFT 4 20 #define MAX8997_LED_MOVIE_CUR_MASK 0xf0 21 22 #define MAX8997_LED_FLASH_MAX_BRIGHTNESS 0x1f 23 #define MAX8997_LED_MOVIE_MAX_BRIGHTNESS 0xf 24 #define MAX8997_LED_NONE_MAX_BRIGHTNESS 0 25 26 #define MAX8997_LED0_FLASH_MASK 0x1 27 #define MAX8997_LED0_FLASH_PIN_MASK 0x5 28 #define MAX8997_LED0_MOVIE_MASK 0x8 29 #define MAX8997_LED0_MOVIE_PIN_MASK 0x28 30 31 #define MAX8997_LED1_FLASH_MASK 0x2 32 #define MAX8997_LED1_FLASH_PIN_MASK 0x6 33 #define MAX8997_LED1_MOVIE_MASK 0x10 34 #define MAX8997_LED1_MOVIE_PIN_MASK 0x30 35 36 #define MAX8997_LED_BOOST_ENABLE_MASK (1 << 6) 37 38 struct max8997_led { 39 struct max8997_dev *iodev; 40 struct led_classdev cdev; 41 bool enabled; 42 int id; 43 enum max8997_led_mode led_mode; 44 struct mutex mutex; 45 }; 46 47 static void max8997_led_set_mode(struct max8997_led *led, 48 enum max8997_led_mode mode) 49 { 50 int ret; 51 struct i2c_client *client = led->iodev->i2c; 52 u8 mask = 0, val; 53 54 switch (mode) { 55 case MAX8997_FLASH_MODE: 56 mask = MAX8997_LED1_FLASH_MASK | MAX8997_LED0_FLASH_MASK; 57 val = led->id ? 58 MAX8997_LED1_FLASH_MASK : MAX8997_LED0_FLASH_MASK; 59 led->cdev.max_brightness = MAX8997_LED_FLASH_MAX_BRIGHTNESS; 60 break; 61 case MAX8997_MOVIE_MODE: 62 mask = MAX8997_LED1_MOVIE_MASK | MAX8997_LED0_MOVIE_MASK; 63 val = led->id ? 64 MAX8997_LED1_MOVIE_MASK : MAX8997_LED0_MOVIE_MASK; 65 led->cdev.max_brightness = MAX8997_LED_MOVIE_MAX_BRIGHTNESS; 66 break; 67 case MAX8997_FLASH_PIN_CONTROL_MODE: 68 mask = MAX8997_LED1_FLASH_PIN_MASK | 69 MAX8997_LED0_FLASH_PIN_MASK; 70 val = led->id ? 71 MAX8997_LED1_FLASH_PIN_MASK : MAX8997_LED0_FLASH_PIN_MASK; 72 led->cdev.max_brightness = MAX8997_LED_FLASH_MAX_BRIGHTNESS; 73 break; 74 case MAX8997_MOVIE_PIN_CONTROL_MODE: 75 mask = MAX8997_LED1_MOVIE_PIN_MASK | 76 MAX8997_LED0_MOVIE_PIN_MASK; 77 val = led->id ? 78 MAX8997_LED1_MOVIE_PIN_MASK : MAX8997_LED0_MOVIE_PIN_MASK; 79 led->cdev.max_brightness = MAX8997_LED_MOVIE_MAX_BRIGHTNESS; 80 break; 81 default: 82 led->cdev.max_brightness = MAX8997_LED_NONE_MAX_BRIGHTNESS; 83 break; 84 } 85 86 if (mask) { 87 ret = max8997_update_reg(client, MAX8997_REG_LEN_CNTL, val, 88 mask); 89 if (ret) 90 dev_err(led->iodev->dev, 91 "failed to update register(%d)\n", ret); 92 } 93 94 led->led_mode = mode; 95 } 96 97 static void max8997_led_enable(struct max8997_led *led, bool enable) 98 { 99 int ret; 100 struct i2c_client *client = led->iodev->i2c; 101 u8 val = 0, mask = MAX8997_LED_BOOST_ENABLE_MASK; 102 103 if (led->enabled == enable) 104 return; 105 106 val = enable ? MAX8997_LED_BOOST_ENABLE_MASK : 0; 107 108 ret = max8997_update_reg(client, MAX8997_REG_BOOST_CNTL, val, mask); 109 if (ret) 110 dev_err(led->iodev->dev, 111 "failed to update register(%d)\n", ret); 112 113 led->enabled = enable; 114 } 115 116 static void max8997_led_set_current(struct max8997_led *led, 117 enum led_brightness value) 118 { 119 int ret; 120 struct i2c_client *client = led->iodev->i2c; 121 u8 val = 0, mask = 0, reg = 0; 122 123 switch (led->led_mode) { 124 case MAX8997_FLASH_MODE: 125 case MAX8997_FLASH_PIN_CONTROL_MODE: 126 val = value << MAX8997_LED_FLASH_SHIFT; 127 mask = MAX8997_LED_FLASH_CUR_MASK; 128 reg = led->id ? MAX8997_REG_FLASH2_CUR : MAX8997_REG_FLASH1_CUR; 129 break; 130 case MAX8997_MOVIE_MODE: 131 case MAX8997_MOVIE_PIN_CONTROL_MODE: 132 val = value << MAX8997_LED_MOVIE_SHIFT; 133 mask = MAX8997_LED_MOVIE_CUR_MASK; 134 reg = MAX8997_REG_MOVIE_CUR; 135 break; 136 default: 137 break; 138 } 139 140 if (mask) { 141 ret = max8997_update_reg(client, reg, val, mask); 142 if (ret) 143 dev_err(led->iodev->dev, 144 "failed to update register(%d)\n", ret); 145 } 146 } 147 148 static void max8997_led_brightness_set(struct led_classdev *led_cdev, 149 enum led_brightness value) 150 { 151 struct max8997_led *led = 152 container_of(led_cdev, struct max8997_led, cdev); 153 154 if (value) { 155 max8997_led_set_current(led, value); 156 max8997_led_enable(led, true); 157 } else { 158 max8997_led_set_current(led, value); 159 max8997_led_enable(led, false); 160 } 161 } 162 163 static ssize_t mode_show(struct device *dev, 164 struct device_attribute *attr, char *buf) 165 { 166 struct led_classdev *led_cdev = dev_get_drvdata(dev); 167 struct max8997_led *led = 168 container_of(led_cdev, struct max8997_led, cdev); 169 ssize_t ret = 0; 170 171 mutex_lock(&led->mutex); 172 173 switch (led->led_mode) { 174 case MAX8997_FLASH_MODE: 175 ret += sprintf(buf, "FLASH\n"); 176 break; 177 case MAX8997_MOVIE_MODE: 178 ret += sprintf(buf, "MOVIE\n"); 179 break; 180 case MAX8997_FLASH_PIN_CONTROL_MODE: 181 ret += sprintf(buf, "FLASH_PIN_CONTROL\n"); 182 break; 183 case MAX8997_MOVIE_PIN_CONTROL_MODE: 184 ret += sprintf(buf, "MOVIE_PIN_CONTROL\n"); 185 break; 186 default: 187 ret += sprintf(buf, "NONE\n"); 188 break; 189 } 190 191 mutex_unlock(&led->mutex); 192 193 return ret; 194 } 195 196 static ssize_t mode_store(struct device *dev, 197 struct device_attribute *attr, 198 const char *buf, size_t size) 199 { 200 struct led_classdev *led_cdev = dev_get_drvdata(dev); 201 struct max8997_led *led = 202 container_of(led_cdev, struct max8997_led, cdev); 203 enum max8997_led_mode mode; 204 205 mutex_lock(&led->mutex); 206 207 if (!strncmp(buf, "FLASH_PIN_CONTROL", 17)) 208 mode = MAX8997_FLASH_PIN_CONTROL_MODE; 209 else if (!strncmp(buf, "MOVIE_PIN_CONTROL", 17)) 210 mode = MAX8997_MOVIE_PIN_CONTROL_MODE; 211 else if (!strncmp(buf, "FLASH", 5)) 212 mode = MAX8997_FLASH_MODE; 213 else if (!strncmp(buf, "MOVIE", 5)) 214 mode = MAX8997_MOVIE_MODE; 215 else 216 mode = MAX8997_NONE; 217 218 max8997_led_set_mode(led, mode); 219 220 mutex_unlock(&led->mutex); 221 222 return size; 223 } 224 225 static DEVICE_ATTR_RW(mode); 226 227 static struct attribute *max8997_attrs[] = { 228 &dev_attr_mode.attr, 229 NULL 230 }; 231 ATTRIBUTE_GROUPS(max8997); 232 233 static int max8997_led_probe(struct platform_device *pdev) 234 { 235 struct max8997_dev *iodev = dev_get_drvdata(pdev->dev.parent); 236 struct max8997_platform_data *pdata = dev_get_platdata(iodev->dev); 237 struct max8997_led *led; 238 char name[20]; 239 int ret = 0; 240 241 if (pdata == NULL) { 242 dev_err(&pdev->dev, "no platform data\n"); 243 return -ENODEV; 244 } 245 246 led = devm_kzalloc(&pdev->dev, sizeof(*led), GFP_KERNEL); 247 if (led == NULL) 248 return -ENOMEM; 249 250 led->id = pdev->id; 251 snprintf(name, sizeof(name), "max8997-led%d", pdev->id); 252 253 led->cdev.name = name; 254 led->cdev.brightness_set = max8997_led_brightness_set; 255 led->cdev.flags |= LED_CORE_SUSPENDRESUME; 256 led->cdev.brightness = 0; 257 led->cdev.groups = max8997_groups; 258 led->iodev = iodev; 259 260 /* initialize mode and brightness according to platform_data */ 261 if (pdata->led_pdata) { 262 u8 mode = 0, brightness = 0; 263 264 mode = pdata->led_pdata->mode[led->id]; 265 brightness = pdata->led_pdata->brightness[led->id]; 266 267 max8997_led_set_mode(led, mode); 268 269 if (brightness > led->cdev.max_brightness) 270 brightness = led->cdev.max_brightness; 271 max8997_led_set_current(led, brightness); 272 led->cdev.brightness = brightness; 273 } else { 274 max8997_led_set_mode(led, MAX8997_NONE); 275 max8997_led_set_current(led, 0); 276 } 277 278 mutex_init(&led->mutex); 279 280 ret = devm_led_classdev_register(&pdev->dev, &led->cdev); 281 if (ret < 0) 282 return ret; 283 284 return 0; 285 } 286 287 static struct platform_driver max8997_led_driver = { 288 .driver = { 289 .name = "max8997-led", 290 }, 291 .probe = max8997_led_probe, 292 }; 293 294 module_platform_driver(max8997_led_driver); 295 296 MODULE_AUTHOR("Donggeun Kim <dg77.kim@samsung.com>"); 297 MODULE_DESCRIPTION("MAX8997 LED driver"); 298 MODULE_LICENSE("GPL"); 299 MODULE_ALIAS("platform:max8997-led"); 300