1 /* Copyright (c) 2010, 2011, 2016 The Linux Foundation. All rights reserved. 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 #include <linux/leds.h> 13 #include <linux/module.h> 14 #include <linux/of.h> 15 #include <linux/of_device.h> 16 #include <linux/platform_device.h> 17 #include <linux/pm.h> 18 #include <linux/regmap.h> 19 20 #define PM8058_LED_TYPE_COMMON 0x00 21 #define PM8058_LED_TYPE_KEYPAD 0x01 22 #define PM8058_LED_TYPE_FLASH 0x02 23 24 #define PM8058_LED_TYPE_COMMON_MASK 0xf8 25 #define PM8058_LED_TYPE_KEYPAD_MASK 0xf0 26 #define PM8058_LED_TYPE_COMMON_SHIFT 3 27 #define PM8058_LED_TYPE_KEYPAD_SHIFT 4 28 29 struct pm8058_led { 30 struct regmap *map; 31 u32 reg; 32 u32 ledtype; 33 struct led_classdev cdev; 34 }; 35 36 static void pm8058_led_set(struct led_classdev *cled, 37 enum led_brightness value) 38 { 39 struct pm8058_led *led; 40 int ret = 0; 41 unsigned int mask = 0; 42 unsigned int val = 0; 43 44 led = container_of(cled, struct pm8058_led, cdev); 45 switch (led->ledtype) { 46 case PM8058_LED_TYPE_COMMON: 47 mask = PM8058_LED_TYPE_COMMON_MASK; 48 val = value << PM8058_LED_TYPE_COMMON_SHIFT; 49 break; 50 case PM8058_LED_TYPE_KEYPAD: 51 case PM8058_LED_TYPE_FLASH: 52 mask = PM8058_LED_TYPE_KEYPAD_MASK; 53 val = value << PM8058_LED_TYPE_KEYPAD_SHIFT; 54 break; 55 default: 56 break; 57 } 58 59 ret = regmap_update_bits(led->map, led->reg, mask, val); 60 if (ret) 61 pr_err("Failed to set LED brightness\n"); 62 } 63 64 static enum led_brightness pm8058_led_get(struct led_classdev *cled) 65 { 66 struct pm8058_led *led; 67 int ret; 68 unsigned int val; 69 70 led = container_of(cled, struct pm8058_led, cdev); 71 72 ret = regmap_read(led->map, led->reg, &val); 73 if (ret) { 74 pr_err("Failed to get LED brightness\n"); 75 return LED_OFF; 76 } 77 78 switch (led->ledtype) { 79 case PM8058_LED_TYPE_COMMON: 80 val &= PM8058_LED_TYPE_COMMON_MASK; 81 val >>= PM8058_LED_TYPE_COMMON_SHIFT; 82 break; 83 case PM8058_LED_TYPE_KEYPAD: 84 case PM8058_LED_TYPE_FLASH: 85 val &= PM8058_LED_TYPE_KEYPAD_MASK; 86 val >>= PM8058_LED_TYPE_KEYPAD_SHIFT; 87 break; 88 default: 89 val = LED_OFF; 90 break; 91 } 92 93 return val; 94 } 95 96 static int pm8058_led_probe(struct platform_device *pdev) 97 { 98 struct pm8058_led *led; 99 struct device_node *np = pdev->dev.of_node; 100 int ret; 101 struct regmap *map; 102 const char *state; 103 enum led_brightness maxbright; 104 105 led = devm_kzalloc(&pdev->dev, sizeof(*led), GFP_KERNEL); 106 if (!led) 107 return -ENOMEM; 108 109 led->ledtype = (u32)of_device_get_match_data(&pdev->dev); 110 111 map = dev_get_regmap(pdev->dev.parent, NULL); 112 if (!map) { 113 dev_err(&pdev->dev, "Parent regmap unavailable.\n"); 114 return -ENXIO; 115 } 116 led->map = map; 117 118 ret = of_property_read_u32(np, "reg", &led->reg); 119 if (ret) { 120 dev_err(&pdev->dev, "no register offset specified\n"); 121 return -EINVAL; 122 } 123 124 /* Use label else node name */ 125 led->cdev.name = of_get_property(np, "label", NULL) ? : np->name; 126 led->cdev.default_trigger = 127 of_get_property(np, "linux,default-trigger", NULL); 128 led->cdev.brightness_set = pm8058_led_set; 129 led->cdev.brightness_get = pm8058_led_get; 130 if (led->ledtype == PM8058_LED_TYPE_COMMON) 131 maxbright = 31; /* 5 bits */ 132 else 133 maxbright = 15; /* 4 bits */ 134 led->cdev.max_brightness = maxbright; 135 136 state = of_get_property(np, "default-state", NULL); 137 if (state) { 138 if (!strcmp(state, "keep")) { 139 led->cdev.brightness = pm8058_led_get(&led->cdev); 140 } else if (!strcmp(state, "on")) { 141 led->cdev.brightness = maxbright; 142 pm8058_led_set(&led->cdev, maxbright); 143 } else { 144 led->cdev.brightness = LED_OFF; 145 pm8058_led_set(&led->cdev, LED_OFF); 146 } 147 } 148 149 if (led->ledtype == PM8058_LED_TYPE_KEYPAD || 150 led->ledtype == PM8058_LED_TYPE_FLASH) 151 led->cdev.flags = LED_CORE_SUSPENDRESUME; 152 153 ret = devm_led_classdev_register(&pdev->dev, &led->cdev); 154 if (ret) { 155 dev_err(&pdev->dev, "unable to register led \"%s\"\n", 156 led->cdev.name); 157 return ret; 158 } 159 160 return 0; 161 } 162 163 static const struct of_device_id pm8058_leds_id_table[] = { 164 { 165 .compatible = "qcom,pm8058-led", 166 .data = (void *)PM8058_LED_TYPE_COMMON 167 }, 168 { 169 .compatible = "qcom,pm8058-keypad-led", 170 .data = (void *)PM8058_LED_TYPE_KEYPAD 171 }, 172 { 173 .compatible = "qcom,pm8058-flash-led", 174 .data = (void *)PM8058_LED_TYPE_FLASH 175 }, 176 { }, 177 }; 178 MODULE_DEVICE_TABLE(of, pm8058_leds_id_table); 179 180 static struct platform_driver pm8058_led_driver = { 181 .probe = pm8058_led_probe, 182 .driver = { 183 .name = "pm8058-leds", 184 .of_match_table = pm8058_leds_id_table, 185 }, 186 }; 187 module_platform_driver(pm8058_led_driver); 188 189 MODULE_DESCRIPTION("PM8058 LEDs driver"); 190 MODULE_LICENSE("GPL v2"); 191 MODULE_ALIAS("platform:pm8058-leds"); 192