423 lines · c
1// SPDX-License-Identifier: GPL-2.0-or-later2/*3 * Driver for MPS MP3309C White LED driver with I2C interface4 *5 * This driver support both analog (by I2C commands) and PWM dimming control6 * modes.7 *8 * Copyright (C) 2023 ASEM Srl9 * Author: Flavio Suligoi <f.suligoi@asem.it>10 *11 * Based on pwm_bl.c12 */13 14#include <linux/backlight.h>15#include <linux/delay.h>16#include <linux/gpio/consumer.h>17#include <linux/i2c.h>18#include <linux/mod_devicetable.h>19#include <linux/property.h>20#include <linux/pwm.h>21#include <linux/regmap.h>22 23#define REG_I2C_0 0x0024#define REG_I2C_1 0x0125 26#define REG_I2C_0_EN 0x8027#define REG_I2C_0_D0 0x4028#define REG_I2C_0_D1 0x2029#define REG_I2C_0_D2 0x1030#define REG_I2C_0_D3 0x0831#define REG_I2C_0_D4 0x0432#define REG_I2C_0_RSRV1 0x0233#define REG_I2C_0_RSRV2 0x0134 35#define REG_I2C_1_RSRV1 0x8036#define REG_I2C_1_DIMS 0x4037#define REG_I2C_1_SYNC 0x2038#define REG_I2C_1_OVP0 0x1039#define REG_I2C_1_OVP1 0x0840#define REG_I2C_1_VOS 0x0441#define REG_I2C_1_LEDO 0x0242#define REG_I2C_1_OTP 0x0143 44#define ANALOG_I2C_NUM_LEVELS 32 /* 0..31 */45#define ANALOG_I2C_REG_MASK 0x7c46 47#define MP3309C_PWM_DEFAULT_NUM_LEVELS 256 /* 0..255 */48 49enum mp3309c_status_value {50 FIRST_POWER_ON,51 BACKLIGHT_OFF,52 BACKLIGHT_ON,53};54 55enum mp3309c_dimming_mode_value {56 DIMMING_PWM,57 DIMMING_ANALOG_I2C,58};59 60struct mp3309c_platform_data {61 unsigned int max_brightness;62 unsigned int default_brightness;63 unsigned int *levels;64 u8 dimming_mode;65 u8 over_voltage_protection;66 bool sync_mode;67 u8 status;68};69 70struct mp3309c_chip {71 struct device *dev;72 struct mp3309c_platform_data *pdata;73 struct backlight_device *bl;74 struct gpio_desc *enable_gpio;75 struct regmap *regmap;76 struct pwm_device *pwmd;77};78 79static const struct regmap_config mp3309c_regmap = {80 .name = "mp3309c_regmap",81 .reg_bits = 8,82 .reg_stride = 1,83 .val_bits = 8,84 .max_register = REG_I2C_1,85};86 87static int mp3309c_enable_device(struct mp3309c_chip *chip)88{89 u8 reg_val;90 int ret;91 92 /* I2C register #0 - Device enable */93 ret = regmap_update_bits(chip->regmap, REG_I2C_0, REG_I2C_0_EN,94 REG_I2C_0_EN);95 if (ret)96 return ret;97 98 /*99 * I2C register #1 - Set working mode:100 * - enable/disable synchronous mode101 * - set overvoltage protection (OVP)102 */103 reg_val = 0x00;104 if (chip->pdata->sync_mode)105 reg_val |= REG_I2C_1_SYNC;106 reg_val |= chip->pdata->over_voltage_protection;107 ret = regmap_write(chip->regmap, REG_I2C_1, reg_val);108 if (ret)109 return ret;110 111 return 0;112}113 114static int mp3309c_bl_update_status(struct backlight_device *bl)115{116 struct mp3309c_chip *chip = bl_get_data(bl);117 int brightness = backlight_get_brightness(bl);118 struct pwm_state pwmstate;119 unsigned int analog_val, bits_val;120 int i, ret;121 122 if (chip->pdata->dimming_mode == DIMMING_PWM) {123 /*124 * PWM control mode125 */126 pwm_get_state(chip->pwmd, &pwmstate);127 pwm_set_relative_duty_cycle(&pwmstate,128 chip->pdata->levels[brightness],129 chip->pdata->levels[chip->pdata->max_brightness]);130 pwmstate.enabled = true;131 ret = pwm_apply_might_sleep(chip->pwmd, &pwmstate);132 if (ret)133 return ret;134 135 switch (chip->pdata->status) {136 case FIRST_POWER_ON:137 case BACKLIGHT_OFF:138 /*139 * After 20ms of low pwm signal level, the chip turns140 * off automatically. In this case, before enabling the141 * chip again, we must wait about 10ms for pwm signal to142 * stabilize.143 */144 if (brightness > 0) {145 msleep(10);146 mp3309c_enable_device(chip);147 chip->pdata->status = BACKLIGHT_ON;148 } else {149 chip->pdata->status = BACKLIGHT_OFF;150 }151 break;152 case BACKLIGHT_ON:153 if (brightness == 0)154 chip->pdata->status = BACKLIGHT_OFF;155 break;156 }157 } else {158 /*159 * Analog (by I2C command) control mode160 *161 * The first time, before setting brightness, we must enable the162 * device163 */164 if (chip->pdata->status == FIRST_POWER_ON)165 mp3309c_enable_device(chip);166 167 /*168 * Dimming mode I2C command (fixed dimming range 0..31)169 *170 * The 5 bits of the dimming analog value D4..D0 is allocated171 * in the I2C register #0, in the following way:172 *173 * +--+--+--+--+--+--+--+--+174 * |EN|D0|D1|D2|D3|D4|XX|XX|175 * +--+--+--+--+--+--+--+--+176 */177 analog_val = brightness;178 bits_val = 0;179 for (i = 0; i <= 5; i++)180 bits_val += ((analog_val >> i) & 0x01) << (6 - i);181 ret = regmap_update_bits(chip->regmap, REG_I2C_0,182 ANALOG_I2C_REG_MASK, bits_val);183 if (ret)184 return ret;185 186 if (brightness > 0)187 chip->pdata->status = BACKLIGHT_ON;188 else189 chip->pdata->status = BACKLIGHT_OFF;190 }191 192 return 0;193}194 195static const struct backlight_ops mp3309c_bl_ops = {196 .update_status = mp3309c_bl_update_status,197};198 199static int mp3309c_parse_fwnode(struct mp3309c_chip *chip,200 struct mp3309c_platform_data *pdata)201{202 int ret, i;203 unsigned int tmp_value;204 struct device *dev = chip->dev;205 int num_levels;206 207 if (!dev_fwnode(dev))208 return dev_err_probe(dev, -ENODEV, "failed to get firmware node\n");209 210 /*211 * Dimming mode: the MP3309C provides two dimming control mode:212 *213 * - PWM mode214 * - Analog by I2C control mode (default)215 *216 * I2C control mode is assumed as default but, if the pwms property is217 * found in the backlight node, the mode switches to PWM mode.218 */219 pdata->dimming_mode = DIMMING_ANALOG_I2C;220 if (device_property_present(dev, "pwms")) {221 chip->pwmd = devm_pwm_get(dev, NULL);222 if (IS_ERR(chip->pwmd))223 return dev_err_probe(dev, PTR_ERR(chip->pwmd), "error getting pwm data\n");224 pdata->dimming_mode = DIMMING_PWM;225 pwm_apply_args(chip->pwmd);226 }227 228 /*229 * In I2C control mode the dimming levels (0..31) are fixed by the230 * hardware, while in PWM control mode they can be chosen by the user,231 * to allow nonlinear mappings.232 */233 if (pdata->dimming_mode == DIMMING_ANALOG_I2C) {234 /*235 * Analog (by I2C commands) control mode: fixed 0..31 brightness236 * levels237 */238 num_levels = ANALOG_I2C_NUM_LEVELS;239 240 /* Enable GPIO used in I2C dimming mode only */241 chip->enable_gpio = devm_gpiod_get(dev, "enable", GPIOD_OUT_HIGH);242 if (IS_ERR(chip->enable_gpio))243 return dev_err_probe(dev, PTR_ERR(chip->enable_gpio),244 "error getting enable gpio\n");245 } else {246 /*247 * PWM control mode: check for brightness level in DT248 */249 if (device_property_present(dev, "brightness-levels")) {250 /* Read brightness levels from DT */251 num_levels = device_property_count_u32(dev, "brightness-levels");252 if (num_levels < 2)253 return -EINVAL;254 } else {255 /* Use default brightness levels */256 num_levels = MP3309C_PWM_DEFAULT_NUM_LEVELS;257 }258 }259 260 /* Fill brightness levels array */261 pdata->levels = devm_kcalloc(dev, num_levels, sizeof(*pdata->levels), GFP_KERNEL);262 if (!pdata->levels)263 return -ENOMEM;264 if (device_property_present(dev, "brightness-levels")) {265 ret = device_property_read_u32_array(dev, "brightness-levels",266 pdata->levels, num_levels);267 if (ret < 0)268 return ret;269 } else {270 for (i = 0; i < num_levels; i++)271 pdata->levels[i] = i;272 }273 274 pdata->max_brightness = num_levels - 1;275 276 ret = device_property_read_u32(dev, "default-brightness", &pdata->default_brightness);277 if (ret)278 pdata->default_brightness = pdata->max_brightness;279 if (pdata->default_brightness > pdata->max_brightness) {280 dev_err_probe(dev, -ERANGE, "default brightness exceeds max brightness\n");281 pdata->default_brightness = pdata->max_brightness;282 }283 284 /*285 * Over-voltage protection (OVP)286 *287 * This (optional) property values are:288 *289 * - 13.5V290 * - 24V291 * - 35.5V (hardware default setting)292 *293 * If missing, the default value for OVP is 35.5V294 */295 pdata->over_voltage_protection = REG_I2C_1_OVP1;296 ret = device_property_read_u32(dev, "mps,overvoltage-protection-microvolt", &tmp_value);297 if (!ret) {298 switch (tmp_value) {299 case 13500000:300 pdata->over_voltage_protection = 0x00;301 break;302 case 24000000:303 pdata->over_voltage_protection = REG_I2C_1_OVP0;304 break;305 case 35500000:306 pdata->over_voltage_protection = REG_I2C_1_OVP1;307 break;308 default:309 return -EINVAL;310 }311 }312 313 /* Synchronous (default) and non-synchronous mode */314 pdata->sync_mode = !device_property_read_bool(dev, "mps,no-sync-mode");315 316 return 0;317}318 319static int mp3309c_probe(struct i2c_client *client)320{321 struct device *dev = &client->dev;322 struct mp3309c_platform_data *pdata = dev_get_platdata(dev);323 struct mp3309c_chip *chip;324 struct backlight_properties props;325 struct pwm_state pwmstate;326 int ret;327 328 if (!i2c_check_functionality(client->adapter, I2C_FUNC_I2C))329 return dev_err_probe(dev, -EOPNOTSUPP, "failed to check i2c functionality\n");330 331 chip = devm_kzalloc(dev, sizeof(*chip), GFP_KERNEL);332 if (!chip)333 return -ENOMEM;334 335 chip->dev = dev;336 337 chip->regmap = devm_regmap_init_i2c(client, &mp3309c_regmap);338 if (IS_ERR(chip->regmap))339 return dev_err_probe(dev, PTR_ERR(chip->regmap),340 "failed to allocate register map\n");341 342 i2c_set_clientdata(client, chip);343 344 if (!pdata) {345 pdata = devm_kzalloc(dev, sizeof(*pdata), GFP_KERNEL);346 if (!pdata)347 return -ENOMEM;348 349 ret = mp3309c_parse_fwnode(chip, pdata);350 if (ret)351 return ret;352 }353 chip->pdata = pdata;354 355 /* Backlight properties */356 memset(&props, 0, sizeof(struct backlight_properties));357 props.brightness = pdata->default_brightness;358 props.max_brightness = pdata->max_brightness;359 props.scale = BACKLIGHT_SCALE_LINEAR;360 props.type = BACKLIGHT_RAW;361 props.power = BACKLIGHT_POWER_ON;362 chip->bl = devm_backlight_device_register(dev, "mp3309c", dev, chip,363 &mp3309c_bl_ops, &props);364 if (IS_ERR(chip->bl))365 return dev_err_probe(dev, PTR_ERR(chip->bl),366 "error registering backlight device\n");367 368 /* In PWM dimming mode, enable pwm device */369 if (chip->pdata->dimming_mode == DIMMING_PWM) {370 pwm_init_state(chip->pwmd, &pwmstate);371 pwm_set_relative_duty_cycle(&pwmstate,372 chip->pdata->default_brightness,373 chip->pdata->max_brightness);374 pwmstate.enabled = true;375 ret = pwm_apply_might_sleep(chip->pwmd, &pwmstate);376 if (ret)377 return dev_err_probe(dev, ret, "error setting pwm device\n");378 }379 380 chip->pdata->status = FIRST_POWER_ON;381 backlight_update_status(chip->bl);382 383 return 0;384}385 386static void mp3309c_remove(struct i2c_client *client)387{388 struct mp3309c_chip *chip = i2c_get_clientdata(client);389 struct backlight_device *bl = chip->bl;390 391 bl->props.power = BACKLIGHT_POWER_OFF;392 bl->props.brightness = 0;393 backlight_update_status(chip->bl);394}395 396static const struct of_device_id mp3309c_match_table[] = {397 { .compatible = "mps,mp3309c", },398 { },399};400MODULE_DEVICE_TABLE(of, mp3309c_match_table);401 402static const struct i2c_device_id mp3309c_id[] = {403 { "mp3309c" },404 { }405};406MODULE_DEVICE_TABLE(i2c, mp3309c_id);407 408static struct i2c_driver mp3309c_i2c_driver = {409 .driver = {410 .name = KBUILD_MODNAME,411 .of_match_table = mp3309c_match_table,412 },413 .probe = mp3309c_probe,414 .remove = mp3309c_remove,415 .id_table = mp3309c_id,416};417 418module_i2c_driver(mp3309c_i2c_driver);419 420MODULE_DESCRIPTION("Backlight Driver for MPS MP3309C");421MODULE_AUTHOR("Flavio Suligoi <f.suligoi@asem.it>");422MODULE_LICENSE("GPL");423