1 // SPDX-License-Identifier: GPL-2.0-only 2 /* 3 * Backlight driver for Wolfson Microelectronics WM831x PMICs 4 * 5 * Copyright 2009 Wolfson Microelectonics plc 6 */ 7 8 #include <linux/kernel.h> 9 #include <linux/init.h> 10 #include <linux/platform_device.h> 11 #include <linux/module.h> 12 #include <linux/backlight.h> 13 #include <linux/slab.h> 14 15 #include <linux/mfd/wm831x/core.h> 16 #include <linux/mfd/wm831x/pdata.h> 17 #include <linux/mfd/wm831x/regulator.h> 18 19 struct wm831x_backlight_data { 20 struct wm831x *wm831x; 21 int isink_reg; 22 int current_brightness; 23 }; 24 25 static int wm831x_backlight_set(struct backlight_device *bl, int brightness) 26 { 27 struct wm831x_backlight_data *data = bl_get_data(bl); 28 struct wm831x *wm831x = data->wm831x; 29 int power_up = !data->current_brightness && brightness; 30 int power_down = data->current_brightness && !brightness; 31 int ret; 32 33 if (power_up) { 34 /* Enable the ISINK */ 35 ret = wm831x_set_bits(wm831x, data->isink_reg, 36 WM831X_CS1_ENA, WM831X_CS1_ENA); 37 if (ret < 0) 38 goto err; 39 40 /* Enable the DC-DC */ 41 ret = wm831x_set_bits(wm831x, WM831X_DCDC_ENABLE, 42 WM831X_DC4_ENA, WM831X_DC4_ENA); 43 if (ret < 0) 44 goto err; 45 } 46 47 if (power_down) { 48 /* DCDC first */ 49 ret = wm831x_set_bits(wm831x, WM831X_DCDC_ENABLE, 50 WM831X_DC4_ENA, 0); 51 if (ret < 0) 52 goto err; 53 54 /* ISINK */ 55 ret = wm831x_set_bits(wm831x, data->isink_reg, 56 WM831X_CS1_DRIVE | WM831X_CS1_ENA, 0); 57 if (ret < 0) 58 goto err; 59 } 60 61 /* Set the new brightness */ 62 ret = wm831x_set_bits(wm831x, data->isink_reg, 63 WM831X_CS1_ISEL_MASK, brightness); 64 if (ret < 0) 65 goto err; 66 67 if (power_up) { 68 /* Drive current through the ISINK */ 69 ret = wm831x_set_bits(wm831x, data->isink_reg, 70 WM831X_CS1_DRIVE, WM831X_CS1_DRIVE); 71 if (ret < 0) 72 return ret; 73 } 74 75 data->current_brightness = brightness; 76 77 return 0; 78 79 err: 80 /* If we were in the middle of a power transition always shut down 81 * for safety. 82 */ 83 if (power_up || power_down) { 84 wm831x_set_bits(wm831x, WM831X_DCDC_ENABLE, WM831X_DC4_ENA, 0); 85 wm831x_set_bits(wm831x, data->isink_reg, WM831X_CS1_ENA, 0); 86 } 87 88 return ret; 89 } 90 91 static int wm831x_backlight_update_status(struct backlight_device *bl) 92 { 93 return wm831x_backlight_set(bl, backlight_get_brightness(bl)); 94 } 95 96 static int wm831x_backlight_get_brightness(struct backlight_device *bl) 97 { 98 struct wm831x_backlight_data *data = bl_get_data(bl); 99 100 return data->current_brightness; 101 } 102 103 static const struct backlight_ops wm831x_backlight_ops = { 104 .options = BL_CORE_SUSPENDRESUME, 105 .update_status = wm831x_backlight_update_status, 106 .get_brightness = wm831x_backlight_get_brightness, 107 }; 108 109 static int wm831x_backlight_probe(struct platform_device *pdev) 110 { 111 struct wm831x *wm831x = dev_get_drvdata(pdev->dev.parent); 112 struct wm831x_pdata *wm831x_pdata = dev_get_platdata(pdev->dev.parent); 113 struct wm831x_backlight_pdata *pdata; 114 struct wm831x_backlight_data *data; 115 struct backlight_device *bl; 116 struct backlight_properties props; 117 int ret, i, max_isel, isink_reg, dcdc_cfg; 118 119 /* We need platform data */ 120 if (wm831x_pdata) 121 pdata = wm831x_pdata->backlight; 122 else 123 pdata = NULL; 124 125 if (!pdata) { 126 dev_err(&pdev->dev, "No platform data supplied\n"); 127 return -EINVAL; 128 } 129 130 /* Figure out the maximum current we can use */ 131 for (i = 0; i < WM831X_ISINK_MAX_ISEL; i++) { 132 if (wm831x_isinkv_values[i] > pdata->max_uA) 133 break; 134 } 135 136 if (i == 0) { 137 dev_err(&pdev->dev, "Invalid max_uA: %duA\n", pdata->max_uA); 138 return -EINVAL; 139 } 140 max_isel = i - 1; 141 142 if (pdata->max_uA != wm831x_isinkv_values[max_isel]) 143 dev_warn(&pdev->dev, 144 "Maximum current is %duA not %duA as requested\n", 145 wm831x_isinkv_values[max_isel], pdata->max_uA); 146 147 switch (pdata->isink) { 148 case 1: 149 isink_reg = WM831X_CURRENT_SINK_1; 150 dcdc_cfg = 0; 151 break; 152 case 2: 153 isink_reg = WM831X_CURRENT_SINK_2; 154 dcdc_cfg = WM831X_DC4_FBSRC; 155 break; 156 default: 157 dev_err(&pdev->dev, "Invalid ISINK %d\n", pdata->isink); 158 return -EINVAL; 159 } 160 161 /* Configure the ISINK to use for feedback */ 162 ret = wm831x_reg_unlock(wm831x); 163 if (ret < 0) 164 return ret; 165 166 ret = wm831x_set_bits(wm831x, WM831X_DC4_CONTROL, WM831X_DC4_FBSRC, 167 dcdc_cfg); 168 169 wm831x_reg_lock(wm831x); 170 if (ret < 0) 171 return ret; 172 173 data = devm_kzalloc(&pdev->dev, sizeof(*data), GFP_KERNEL); 174 if (data == NULL) 175 return -ENOMEM; 176 177 data->wm831x = wm831x; 178 data->current_brightness = 0; 179 data->isink_reg = isink_reg; 180 181 memset(&props, 0, sizeof(props)); 182 props.type = BACKLIGHT_RAW; 183 props.max_brightness = max_isel; 184 bl = devm_backlight_device_register(&pdev->dev, "wm831x", &pdev->dev, 185 data, &wm831x_backlight_ops, &props); 186 if (IS_ERR(bl)) { 187 dev_err(&pdev->dev, "failed to register backlight\n"); 188 return PTR_ERR(bl); 189 } 190 191 bl->props.brightness = max_isel; 192 193 platform_set_drvdata(pdev, bl); 194 195 /* Disable the DCDC if it was started so we can bootstrap */ 196 wm831x_set_bits(wm831x, WM831X_DCDC_ENABLE, WM831X_DC4_ENA, 0); 197 198 backlight_update_status(bl); 199 200 return 0; 201 } 202 203 static struct platform_driver wm831x_backlight_driver = { 204 .driver = { 205 .name = "wm831x-backlight", 206 }, 207 .probe = wm831x_backlight_probe, 208 }; 209 210 module_platform_driver(wm831x_backlight_driver); 211 212 MODULE_DESCRIPTION("Backlight Driver for WM831x PMICs"); 213 MODULE_AUTHOR("Mark Brown <broonie@opensource.wolfsonmicro.com"); 214 MODULE_LICENSE("GPL"); 215 MODULE_ALIAS("platform:wm831x-backlight"); 216