1 // SPDX-License-Identifier: GPL-2.0 2 // Copyright (C) 2018 Spreadtrum Communications Inc. 3 4 #include <linux/leds.h> 5 #include <linux/module.h> 6 #include <linux/of.h> 7 #include <linux/platform_device.h> 8 #include <linux/regmap.h> 9 #include <uapi/linux/uleds.h> 10 11 /* PMIC global control register definition */ 12 #define SC27XX_MODULE_EN0 0xc08 13 #define SC27XX_CLK_EN0 0xc18 14 #define SC27XX_RGB_CTRL 0xebc 15 16 #define SC27XX_BLTC_EN BIT(9) 17 #define SC27XX_RTC_EN BIT(7) 18 #define SC27XX_RGB_PD BIT(0) 19 20 /* Breathing light controller register definition */ 21 #define SC27XX_LEDS_CTRL 0x00 22 #define SC27XX_LEDS_PRESCALE 0x04 23 #define SC27XX_LEDS_DUTY 0x08 24 #define SC27XX_LEDS_CURVE0 0x0c 25 #define SC27XX_LEDS_CURVE1 0x10 26 27 #define SC27XX_CTRL_SHIFT 4 28 #define SC27XX_LED_RUN BIT(0) 29 #define SC27XX_LED_TYPE BIT(1) 30 31 #define SC27XX_DUTY_SHIFT 8 32 #define SC27XX_DUTY_MASK GENMASK(15, 0) 33 #define SC27XX_MOD_MASK GENMASK(7, 0) 34 35 #define SC27XX_LEDS_OFFSET 0x10 36 #define SC27XX_LEDS_MAX 3 37 38 struct sc27xx_led { 39 char name[LED_MAX_NAME_SIZE]; 40 struct led_classdev ldev; 41 struct sc27xx_led_priv *priv; 42 u8 line; 43 bool active; 44 }; 45 46 struct sc27xx_led_priv { 47 struct sc27xx_led leds[SC27XX_LEDS_MAX]; 48 struct regmap *regmap; 49 struct mutex lock; 50 u32 base; 51 }; 52 53 #define to_sc27xx_led(ldev) \ 54 container_of(ldev, struct sc27xx_led, ldev) 55 56 static int sc27xx_led_init(struct regmap *regmap) 57 { 58 int err; 59 60 err = regmap_update_bits(regmap, SC27XX_MODULE_EN0, SC27XX_BLTC_EN, 61 SC27XX_BLTC_EN); 62 if (err) 63 return err; 64 65 err = regmap_update_bits(regmap, SC27XX_CLK_EN0, SC27XX_RTC_EN, 66 SC27XX_RTC_EN); 67 if (err) 68 return err; 69 70 return regmap_update_bits(regmap, SC27XX_RGB_CTRL, SC27XX_RGB_PD, 0); 71 } 72 73 static u32 sc27xx_led_get_offset(struct sc27xx_led *leds) 74 { 75 return leds->priv->base + SC27XX_LEDS_OFFSET * leds->line; 76 } 77 78 static int sc27xx_led_enable(struct sc27xx_led *leds, enum led_brightness value) 79 { 80 u32 base = sc27xx_led_get_offset(leds); 81 u32 ctrl_base = leds->priv->base + SC27XX_LEDS_CTRL; 82 u8 ctrl_shift = SC27XX_CTRL_SHIFT * leds->line; 83 struct regmap *regmap = leds->priv->regmap; 84 int err; 85 86 err = regmap_update_bits(regmap, base + SC27XX_LEDS_DUTY, 87 SC27XX_DUTY_MASK, 88 (value << SC27XX_DUTY_SHIFT) | 89 SC27XX_MOD_MASK); 90 if (err) 91 return err; 92 93 return regmap_update_bits(regmap, ctrl_base, 94 (SC27XX_LED_RUN | SC27XX_LED_TYPE) << ctrl_shift, 95 (SC27XX_LED_RUN | SC27XX_LED_TYPE) << ctrl_shift); 96 } 97 98 static int sc27xx_led_disable(struct sc27xx_led *leds) 99 { 100 struct regmap *regmap = leds->priv->regmap; 101 u32 ctrl_base = leds->priv->base + SC27XX_LEDS_CTRL; 102 u8 ctrl_shift = SC27XX_CTRL_SHIFT * leds->line; 103 104 return regmap_update_bits(regmap, ctrl_base, 105 (SC27XX_LED_RUN | SC27XX_LED_TYPE) << ctrl_shift, 0); 106 } 107 108 static int sc27xx_led_set(struct led_classdev *ldev, enum led_brightness value) 109 { 110 struct sc27xx_led *leds = to_sc27xx_led(ldev); 111 int err; 112 113 mutex_lock(&leds->priv->lock); 114 115 if (value == LED_OFF) 116 err = sc27xx_led_disable(leds); 117 else 118 err = sc27xx_led_enable(leds, value); 119 120 mutex_unlock(&leds->priv->lock); 121 122 return err; 123 } 124 125 static int sc27xx_led_register(struct device *dev, struct sc27xx_led_priv *priv) 126 { 127 int i, err; 128 129 err = sc27xx_led_init(priv->regmap); 130 if (err) 131 return err; 132 133 for (i = 0; i < SC27XX_LEDS_MAX; i++) { 134 struct sc27xx_led *led = &priv->leds[i]; 135 136 if (!led->active) 137 continue; 138 139 led->line = i; 140 led->priv = priv; 141 led->ldev.name = led->name; 142 led->ldev.brightness_set_blocking = sc27xx_led_set; 143 144 err = devm_led_classdev_register(dev, &led->ldev); 145 if (err) 146 return err; 147 } 148 149 return 0; 150 } 151 152 static int sc27xx_led_probe(struct platform_device *pdev) 153 { 154 struct device *dev = &pdev->dev; 155 struct device_node *np = dev->of_node, *child; 156 struct sc27xx_led_priv *priv; 157 const char *str; 158 u32 base, count, reg; 159 int err; 160 161 count = of_get_child_count(np); 162 if (!count || count > SC27XX_LEDS_MAX) 163 return -EINVAL; 164 165 err = of_property_read_u32(np, "reg", &base); 166 if (err) { 167 dev_err(dev, "fail to get reg of property\n"); 168 return err; 169 } 170 171 priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL); 172 if (!priv) 173 return -ENOMEM; 174 175 platform_set_drvdata(pdev, priv); 176 mutex_init(&priv->lock); 177 priv->base = base; 178 priv->regmap = dev_get_regmap(dev->parent, NULL); 179 if (!priv->regmap) { 180 err = -ENODEV; 181 dev_err(dev, "failed to get regmap: %d\n", err); 182 return err; 183 } 184 185 for_each_child_of_node(np, child) { 186 err = of_property_read_u32(child, "reg", ®); 187 if (err) { 188 of_node_put(child); 189 mutex_destroy(&priv->lock); 190 return err; 191 } 192 193 if (reg >= SC27XX_LEDS_MAX || priv->leds[reg].active) { 194 of_node_put(child); 195 mutex_destroy(&priv->lock); 196 return -EINVAL; 197 } 198 199 priv->leds[reg].active = true; 200 201 err = of_property_read_string(child, "label", &str); 202 if (err) 203 snprintf(priv->leds[reg].name, LED_MAX_NAME_SIZE, 204 "sc27xx::"); 205 else 206 snprintf(priv->leds[reg].name, LED_MAX_NAME_SIZE, 207 "sc27xx:%s", str); 208 } 209 210 err = sc27xx_led_register(dev, priv); 211 if (err) 212 mutex_destroy(&priv->lock); 213 214 return err; 215 } 216 217 static int sc27xx_led_remove(struct platform_device *pdev) 218 { 219 struct sc27xx_led_priv *priv = platform_get_drvdata(pdev); 220 221 mutex_destroy(&priv->lock); 222 return 0; 223 } 224 225 static const struct of_device_id sc27xx_led_of_match[] = { 226 { .compatible = "sprd,sc2731-bltc", }, 227 { } 228 }; 229 MODULE_DEVICE_TABLE(of, sc27xx_led_of_match); 230 231 static struct platform_driver sc27xx_led_driver = { 232 .driver = { 233 .name = "sprd-bltc", 234 .of_match_table = sc27xx_led_of_match, 235 }, 236 .probe = sc27xx_led_probe, 237 .remove = sc27xx_led_remove, 238 }; 239 240 module_platform_driver(sc27xx_led_driver); 241 242 MODULE_DESCRIPTION("Spreadtrum SC27xx breathing light controller driver"); 243 MODULE_AUTHOR("Xiaotong Lu <xiaotong.lu@spreadtrum.com>"); 244 MODULE_LICENSE("GPL v2"); 245