728 lines · c
1// SPDX-License-Identifier: GPL-2.0-or-later2/*3 * LED driver for Mediatek MT6323 PMIC4 *5 * Copyright (C) 2017 Sean Wang <sean.wang@mediatek.com>6 */7#include <linux/kernel.h>8#include <linux/leds.h>9#include <linux/mfd/mt6323/registers.h>10#include <linux/mfd/mt6397/core.h>11#include <linux/module.h>12#include <linux/of.h>13#include <linux/platform_device.h>14#include <linux/regmap.h>15 16/*17 * Register field for TOP_CKPDN0 to enable18 * 32K clock common for LED device.19 */20#define RG_DRV_32K_CK_PDN BIT(11)21#define RG_DRV_32K_CK_PDN_MASK BIT(11)22 23/* 32K/1M/6M clock common for WLED device */24#define RG_VWLED_1M_CK_PDN BIT(0)25#define RG_VWLED_32K_CK_PDN BIT(12)26#define RG_VWLED_6M_CK_PDN BIT(13)27 28/*29 * Register field for TOP_CKPDN2 to enable30 * individual clock for LED device.31 */32#define RG_ISINK_CK_PDN(i) BIT(i)33#define RG_ISINK_CK_PDN_MASK(i) BIT(i)34 35/*36 * Register field for TOP_CKCON1 to select37 * clock source.38 */39#define RG_ISINK_CK_SEL_MASK(i) (BIT(10) << (i))40 41#define ISINK_CON(r, i) (r + 0x8 * (i))42 43/* ISINK_CON0: Register to setup the duty cycle of the blink. */44#define ISINK_DIM_DUTY_MASK (0x1f << 8)45#define ISINK_DIM_DUTY(i) (((i) << 8) & ISINK_DIM_DUTY_MASK)46 47/* ISINK_CON1: Register to setup the period of the blink. */48#define ISINK_DIM_FSEL_MASK (0xffff)49#define ISINK_DIM_FSEL(i) ((i) & ISINK_DIM_FSEL_MASK)50 51/* ISINK_CON2: Register to control the brightness. */52#define ISINK_CH_STEP_SHIFT 1253#define ISINK_CH_STEP_MASK (0x7 << 12)54#define ISINK_CH_STEP(i) (((i) << 12) & ISINK_CH_STEP_MASK)55#define ISINK_SFSTR0_TC_MASK (0x3 << 1)56#define ISINK_SFSTR0_TC(i) (((i) << 1) & ISINK_SFSTR0_TC_MASK)57#define ISINK_SFSTR0_EN_MASK BIT(0)58#define ISINK_SFSTR0_EN BIT(0)59 60/* Register to LED channel enablement. */61#define ISINK_CH_EN_MASK(i) BIT(i)62#define ISINK_CH_EN(i) BIT(i)63 64#define MAX_SUPPORTED_LEDS 865 66struct mt6323_leds;67 68/**69 * struct mt6323_led - state container for the LED device70 * @id: the identifier in MT6323 LED device71 * @parent: the pointer to MT6323 LED controller72 * @cdev: LED class device for this LED device73 * @current_brightness: current state of the LED device74 */75struct mt6323_led {76 int id;77 struct mt6323_leds *parent;78 struct led_classdev cdev;79 enum led_brightness current_brightness;80};81 82/**83 * struct mt6323_regs - register spec for the LED device84 * @top_ckpdn: Offset to ISINK_CKPDN[0..x] registers85 * @num_top_ckpdn: Number of ISINK_CKPDN registers86 * @top_ckcon: Offset to ISINK_CKCON[0..x] registers87 * @num_top_ckcon: Number of ISINK_CKCON registers88 * @isink_con: Offset to ISINKx_CON[0..x] registers89 * @num_isink_con: Number of ISINKx_CON registers90 * @isink_max_regs: Number of ISINK[0..x] registers91 * @isink_en_ctrl: Offset to ISINK_EN_CTRL register92 * @iwled_en_ctrl: Offset to IWLED_EN_CTRL register93 */94struct mt6323_regs {95 const u16 *top_ckpdn;96 u8 num_top_ckpdn;97 const u16 *top_ckcon;98 u8 num_top_ckcon;99 const u16 *isink_con;100 u8 num_isink_con;101 u8 isink_max_regs;102 u16 isink_en_ctrl;103 u16 iwled_en_ctrl;104};105 106/**107 * struct mt6323_hwspec - hardware specific parameters108 * @max_period: Maximum period for all LEDs109 * @max_leds: Maximum number of supported LEDs110 * @max_wleds: Maximum number of WLEDs111 * @max_brightness: Maximum brightness for all LEDs112 * @unit_duty: Steps of duty per period113 */114struct mt6323_hwspec {115 u16 max_period;116 u8 max_leds;117 u8 max_wleds;118 u16 max_brightness;119 u16 unit_duty;120};121 122/**123 * struct mt6323_data - device specific data124 * @regs: Register spec for this device125 * @spec: Hardware specific parameters126 */127struct mt6323_data {128 const struct mt6323_regs *regs;129 const struct mt6323_hwspec *spec;130};131 132/**133 * struct mt6323_leds - state container for holding LED controller134 * of the driver135 * @dev: the device pointer136 * @hw: the underlying hardware providing shared137 * bus for the register operations138 * @pdata: device specific data139 * @lock: the lock among process context140 * @led: the array that contains the state of individual141 * LED device142 */143struct mt6323_leds {144 struct device *dev;145 struct mt6397_chip *hw;146 const struct mt6323_data *pdata;147 /* protect among process context */148 struct mutex lock;149 struct mt6323_led *led[MAX_SUPPORTED_LEDS];150};151 152static int mt6323_led_hw_brightness(struct led_classdev *cdev,153 enum led_brightness brightness)154{155 struct mt6323_led *led = container_of(cdev, struct mt6323_led, cdev);156 struct mt6323_leds *leds = led->parent;157 const struct mt6323_regs *regs = leds->pdata->regs;158 struct regmap *regmap = leds->hw->regmap;159 u32 con2_mask = 0, con2_val = 0;160 int ret;161 162 /*163 * Setup current output for the corresponding164 * brightness level.165 */166 con2_mask |= ISINK_CH_STEP_MASK |167 ISINK_SFSTR0_TC_MASK |168 ISINK_SFSTR0_EN_MASK;169 con2_val |= ISINK_CH_STEP(brightness - 1) |170 ISINK_SFSTR0_TC(2) |171 ISINK_SFSTR0_EN;172 173 ret = regmap_update_bits(regmap, ISINK_CON(regs->isink_con[2], led->id),174 con2_mask, con2_val);175 return ret;176}177 178static int mt6323_led_hw_off(struct led_classdev *cdev)179{180 struct mt6323_led *led = container_of(cdev, struct mt6323_led, cdev);181 struct mt6323_leds *leds = led->parent;182 const struct mt6323_regs *regs = leds->pdata->regs;183 struct regmap *regmap = leds->hw->regmap;184 unsigned int status;185 int ret;186 187 status = ISINK_CH_EN(led->id);188 ret = regmap_update_bits(regmap, regs->isink_en_ctrl,189 ISINK_CH_EN_MASK(led->id), ~status);190 if (ret < 0)191 return ret;192 193 usleep_range(100, 300);194 ret = regmap_update_bits(regmap, regs->top_ckpdn[2],195 RG_ISINK_CK_PDN_MASK(led->id),196 RG_ISINK_CK_PDN(led->id));197 if (ret < 0)198 return ret;199 200 return 0;201}202 203static enum led_brightness204mt6323_get_led_hw_brightness(struct led_classdev *cdev)205{206 struct mt6323_led *led = container_of(cdev, struct mt6323_led, cdev);207 struct mt6323_leds *leds = led->parent;208 const struct mt6323_regs *regs = leds->pdata->regs;209 struct regmap *regmap = leds->hw->regmap;210 unsigned int status;211 int ret;212 213 ret = regmap_read(regmap, regs->top_ckpdn[2], &status);214 if (ret < 0)215 return ret;216 217 if (status & RG_ISINK_CK_PDN_MASK(led->id))218 return 0;219 220 ret = regmap_read(regmap, regs->isink_en_ctrl, &status);221 if (ret < 0)222 return ret;223 224 if (!(status & ISINK_CH_EN(led->id)))225 return 0;226 227 ret = regmap_read(regmap, ISINK_CON(regs->isink_con[2], led->id), &status);228 if (ret < 0)229 return ret;230 231 return ((status & ISINK_CH_STEP_MASK)232 >> ISINK_CH_STEP_SHIFT) + 1;233}234 235static int mt6323_led_hw_on(struct led_classdev *cdev,236 enum led_brightness brightness)237{238 struct mt6323_led *led = container_of(cdev, struct mt6323_led, cdev);239 struct mt6323_leds *leds = led->parent;240 const struct mt6323_regs *regs = leds->pdata->regs;241 struct regmap *regmap = leds->hw->regmap;242 unsigned int status;243 int ret;244 245 /*246 * Setup required clock source, enable the corresponding247 * clock and channel and let work with continuous blink as248 * the default.249 */250 ret = regmap_update_bits(regmap, regs->top_ckcon[1],251 RG_ISINK_CK_SEL_MASK(led->id), 0);252 if (ret < 0)253 return ret;254 255 status = RG_ISINK_CK_PDN(led->id);256 ret = regmap_update_bits(regmap, regs->top_ckpdn[2],257 RG_ISINK_CK_PDN_MASK(led->id),258 ~status);259 if (ret < 0)260 return ret;261 262 usleep_range(100, 300);263 264 ret = regmap_update_bits(regmap, regs->isink_en_ctrl,265 ISINK_CH_EN_MASK(led->id),266 ISINK_CH_EN(led->id));267 if (ret < 0)268 return ret;269 270 ret = mt6323_led_hw_brightness(cdev, brightness);271 if (ret < 0)272 return ret;273 274 ret = regmap_update_bits(regmap, ISINK_CON(regs->isink_con[0], led->id),275 ISINK_DIM_DUTY_MASK,276 ISINK_DIM_DUTY(31));277 if (ret < 0)278 return ret;279 280 ret = regmap_update_bits(regmap, ISINK_CON(regs->isink_con[1], led->id),281 ISINK_DIM_FSEL_MASK,282 ISINK_DIM_FSEL(1000));283 if (ret < 0)284 return ret;285 286 return 0;287}288 289static int mt6323_led_set_blink(struct led_classdev *cdev,290 unsigned long *delay_on,291 unsigned long *delay_off)292{293 struct mt6323_led *led = container_of(cdev, struct mt6323_led, cdev);294 struct mt6323_leds *leds = led->parent;295 const struct mt6323_regs *regs = leds->pdata->regs;296 const struct mt6323_hwspec *spec = leds->pdata->spec;297 struct regmap *regmap = leds->hw->regmap;298 unsigned long period;299 u8 duty_hw;300 int ret;301 302 /*303 * LED subsystem requires a default user304 * friendly blink pattern for the LED so using305 * 1Hz duty cycle 50% here if without specific306 * value delay_on and delay off being assigned.307 */308 if (!*delay_on && !*delay_off) {309 *delay_on = 500;310 *delay_off = 500;311 }312 313 /*314 * Units are in ms, if over the hardware able315 * to support, fallback into software blink316 */317 period = *delay_on + *delay_off;318 319 if (period > spec->max_period)320 return -EINVAL;321 322 /*323 * Calculate duty_hw based on the percentage of period during324 * which the led is ON.325 */326 duty_hw = DIV_ROUND_CLOSEST(*delay_on * 100000ul, period * spec->unit_duty);327 328 /* hardware doesn't support zero duty cycle. */329 if (!duty_hw)330 return -EINVAL;331 332 mutex_lock(&leds->lock);333 /*334 * Set max_brightness as the software blink behavior335 * when no blink brightness.336 */337 if (!led->current_brightness) {338 ret = mt6323_led_hw_on(cdev, cdev->max_brightness);339 if (ret < 0)340 goto out;341 led->current_brightness = cdev->max_brightness;342 }343 344 ret = regmap_update_bits(regmap, ISINK_CON(regs->isink_con[0], led->id),345 ISINK_DIM_DUTY_MASK,346 ISINK_DIM_DUTY(duty_hw - 1));347 if (ret < 0)348 goto out;349 350 ret = regmap_update_bits(regmap, ISINK_CON(regs->isink_con[1], led->id),351 ISINK_DIM_FSEL_MASK,352 ISINK_DIM_FSEL(period - 1));353out:354 mutex_unlock(&leds->lock);355 356 return ret;357}358 359static int mt6323_led_set_brightness(struct led_classdev *cdev,360 enum led_brightness brightness)361{362 struct mt6323_led *led = container_of(cdev, struct mt6323_led, cdev);363 struct mt6323_leds *leds = led->parent;364 int ret;365 366 mutex_lock(&leds->lock);367 368 if (!led->current_brightness && brightness) {369 ret = mt6323_led_hw_on(cdev, brightness);370 if (ret < 0)371 goto out;372 } else if (brightness) {373 ret = mt6323_led_hw_brightness(cdev, brightness);374 if (ret < 0)375 goto out;376 } else {377 ret = mt6323_led_hw_off(cdev);378 if (ret < 0)379 goto out;380 }381 382 led->current_brightness = brightness;383out:384 mutex_unlock(&leds->lock);385 386 return ret;387}388 389static int mtk_wled_hw_on(struct led_classdev *cdev)390{391 struct mt6323_led *led = container_of(cdev, struct mt6323_led, cdev);392 struct mt6323_leds *leds = led->parent;393 const struct mt6323_regs *regs = leds->pdata->regs;394 struct regmap *regmap = leds->hw->regmap;395 int ret;396 397 ret = regmap_clear_bits(regmap, regs->top_ckpdn[0], RG_VWLED_32K_CK_PDN);398 if (ret)399 return ret;400 401 ret = regmap_clear_bits(regmap, regs->top_ckpdn[0], RG_VWLED_6M_CK_PDN);402 if (ret)403 return ret;404 405 ret = regmap_clear_bits(regmap, regs->top_ckpdn[0], RG_VWLED_1M_CK_PDN);406 if (ret)407 return ret;408 409 usleep_range(5000, 6000);410 411 /* Enable WLED channel pair */412 ret = regmap_set_bits(regmap, regs->iwled_en_ctrl, BIT(led->id));413 if (ret)414 return ret;415 416 ret = regmap_set_bits(regmap, regs->iwled_en_ctrl, BIT(led->id + 1));417 if (ret)418 return ret;419 420 return 0;421}422 423static int mtk_wled_hw_off(struct led_classdev *cdev)424{425 struct mt6323_led *led = container_of(cdev, struct mt6323_led, cdev);426 struct mt6323_leds *leds = led->parent;427 const struct mt6323_regs *regs = leds->pdata->regs;428 struct regmap *regmap = leds->hw->regmap;429 int ret;430 431 ret = regmap_clear_bits(regmap, regs->iwled_en_ctrl, BIT(led->id + 1));432 if (ret)433 return ret;434 435 ret = regmap_clear_bits(regmap, regs->iwled_en_ctrl, BIT(led->id));436 if (ret)437 return ret;438 439 ret = regmap_set_bits(regmap, regs->top_ckpdn[0], RG_VWLED_32K_CK_PDN);440 if (ret)441 return ret;442 443 ret = regmap_set_bits(regmap, regs->top_ckpdn[0], RG_VWLED_6M_CK_PDN);444 if (ret)445 return ret;446 447 ret = regmap_set_bits(regmap, regs->top_ckpdn[0], RG_VWLED_1M_CK_PDN);448 if (ret)449 return ret;450 451 return 0;452}453 454static enum led_brightness mt6323_get_wled_brightness(struct led_classdev *cdev)455{456 struct mt6323_led *led = container_of(cdev, struct mt6323_led, cdev);457 struct mt6323_leds *leds = led->parent;458 const struct mt6323_regs *regs = leds->pdata->regs;459 struct regmap *regmap = leds->hw->regmap;460 unsigned int status;461 int ret;462 463 ret = regmap_read(regmap, regs->iwled_en_ctrl, &status);464 if (ret)465 return 0;466 467 /* Always two channels per WLED */468 status &= BIT(led->id) | BIT(led->id + 1);469 470 return status ? led->current_brightness : 0;471}472 473static int mt6323_wled_set_brightness(struct led_classdev *cdev,474 enum led_brightness brightness)475{476 struct mt6323_led *led = container_of(cdev, struct mt6323_led, cdev);477 struct mt6323_leds *leds = led->parent;478 int ret = 0;479 480 mutex_lock(&leds->lock);481 482 if (brightness) {483 if (!led->current_brightness)484 ret = mtk_wled_hw_on(cdev);485 if (ret)486 goto out;487 } else {488 ret = mtk_wled_hw_off(cdev);489 if (ret)490 goto out;491 }492 493 led->current_brightness = brightness;494out:495 mutex_unlock(&leds->lock);496 497 return ret;498}499 500static int mt6323_led_set_dt_default(struct led_classdev *cdev,501 struct device_node *np)502{503 struct mt6323_led *led = container_of(cdev, struct mt6323_led, cdev);504 enum led_default_state state;505 int ret = 0;506 507 state = led_init_default_state_get(of_fwnode_handle(np));508 switch (state) {509 case LEDS_DEFSTATE_ON:510 ret = mt6323_led_set_brightness(cdev, cdev->max_brightness);511 break;512 case LEDS_DEFSTATE_KEEP:513 ret = mt6323_get_led_hw_brightness(cdev);514 if (ret < 0)515 return ret;516 led->current_brightness = ret;517 ret = 0;518 break;519 default:520 ret = mt6323_led_set_brightness(cdev, LED_OFF);521 }522 523 return ret;524}525 526static int mt6323_led_probe(struct platform_device *pdev)527{528 struct device *dev = &pdev->dev;529 struct device_node *np = dev_of_node(dev);530 struct mt6397_chip *hw = dev_get_drvdata(dev->parent);531 struct mt6323_leds *leds;532 struct mt6323_led *led;533 const struct mt6323_regs *regs;534 const struct mt6323_hwspec *spec;535 int ret;536 unsigned int status;537 u32 reg;538 u8 max_leds;539 540 leds = devm_kzalloc(dev, sizeof(*leds), GFP_KERNEL);541 if (!leds)542 return -ENOMEM;543 544 platform_set_drvdata(pdev, leds);545 leds->dev = dev;546 leds->pdata = device_get_match_data(dev);547 regs = leds->pdata->regs;548 spec = leds->pdata->spec;549 max_leds = spec->max_leds + spec->max_wleds;550 551 /*552 * leds->hw points to the underlying bus for the register553 * controlled.554 */555 leds->hw = hw;556 mutex_init(&leds->lock);557 558 status = RG_DRV_32K_CK_PDN;559 ret = regmap_update_bits(leds->hw->regmap, regs->top_ckpdn[0],560 RG_DRV_32K_CK_PDN_MASK, ~status);561 if (ret < 0) {562 dev_err(leds->dev,563 "Failed to update TOP_CKPDN0 Register\n");564 return ret;565 }566 567 for_each_available_child_of_node_scoped(np, child) {568 struct led_init_data init_data = {};569 bool is_wled;570 571 ret = of_property_read_u32(child, "reg", ®);572 if (ret) {573 dev_err(dev, "Failed to read led 'reg' property\n");574 return ret;575 }576 577 if (reg >= max_leds || reg >= MAX_SUPPORTED_LEDS ||578 leds->led[reg]) {579 dev_err(dev, "Invalid led reg %u\n", reg);580 return -EINVAL;581 }582 583 led = devm_kzalloc(dev, sizeof(*led), GFP_KERNEL);584 if (!led)585 return -ENOMEM;586 587 is_wled = of_property_read_bool(child, "mediatek,is-wled");588 589 leds->led[reg] = led;590 leds->led[reg]->id = reg;591 leds->led[reg]->cdev.max_brightness = spec->max_brightness;592 593 if (is_wled) {594 leds->led[reg]->cdev.brightness_set_blocking =595 mt6323_wled_set_brightness;596 leds->led[reg]->cdev.brightness_get =597 mt6323_get_wled_brightness;598 } else {599 leds->led[reg]->cdev.brightness_set_blocking =600 mt6323_led_set_brightness;601 leds->led[reg]->cdev.blink_set = mt6323_led_set_blink;602 leds->led[reg]->cdev.brightness_get =603 mt6323_get_led_hw_brightness;604 }605 leds->led[reg]->parent = leds;606 607 ret = mt6323_led_set_dt_default(&leds->led[reg]->cdev, child);608 if (ret < 0) {609 dev_err(leds->dev,610 "Failed to LED set default from devicetree\n");611 return ret;612 }613 614 init_data.fwnode = of_fwnode_handle(child);615 616 ret = devm_led_classdev_register_ext(dev, &leds->led[reg]->cdev,617 &init_data);618 if (ret) {619 dev_err(dev, "Failed to register LED: %d\n", ret);620 return ret;621 }622 }623 624 return 0;625}626 627static void mt6323_led_remove(struct platform_device *pdev)628{629 struct mt6323_leds *leds = platform_get_drvdata(pdev);630 const struct mt6323_regs *regs = leds->pdata->regs;631 int i;632 633 /* Turn the LEDs off on driver removal. */634 for (i = 0 ; leds->led[i] ; i++)635 mt6323_led_hw_off(&leds->led[i]->cdev);636 637 regmap_update_bits(leds->hw->regmap, regs->top_ckpdn[0],638 RG_DRV_32K_CK_PDN_MASK,639 RG_DRV_32K_CK_PDN);640 641 mutex_destroy(&leds->lock);642}643 644static const struct mt6323_regs mt6323_registers = {645 .top_ckpdn = (const u16[]){ 0x102, 0x106, 0x10e },646 .num_top_ckpdn = 3,647 .top_ckcon = (const u16[]){ 0x120, 0x126 },648 .num_top_ckcon = 2,649 .isink_con = (const u16[]){ 0x330, 0x332, 0x334 },650 .num_isink_con = 3,651 .isink_max_regs = 4, /* ISINK[0..3] */652 .isink_en_ctrl = 0x356,653};654 655static const struct mt6323_regs mt6331_registers = {656 .top_ckpdn = (const u16[]){ 0x138, 0x13e, 0x144 },657 .num_top_ckpdn = 3,658 .top_ckcon = (const u16[]){ 0x14c, 0x14a },659 .num_top_ckcon = 2,660 .isink_con = (const u16[]){ 0x40c, 0x40e, 0x410, 0x412, 0x414 },661 .num_isink_con = 5,662 .isink_max_regs = 4, /* ISINK[0..3] */663 .isink_en_ctrl = 0x43a,664};665 666static const struct mt6323_regs mt6332_registers = {667 .top_ckpdn = (const u16[]){ 0x8094, 0x809a, 0x80a0 },668 .num_top_ckpdn = 3,669 .top_ckcon = (const u16[]){ 0x80a6, 0x80ac },670 .num_top_ckcon = 2,671 .isink_con = (const u16[]){ 0x8cd4 },672 .num_isink_con = 1,673 .isink_max_regs = 12, /* IWLED[0..2, 3..9] */674 .iwled_en_ctrl = 0x8cda,675};676 677static const struct mt6323_hwspec mt6323_spec = {678 .max_period = 10000,679 .max_leds = 4,680 .max_brightness = 6,681 .unit_duty = 3125,682};683 684static const struct mt6323_hwspec mt6332_spec = {685 /* There are no LEDs in MT6332. Only WLEDs are present. */686 .max_leds = 0,687 .max_wleds = 1,688 .max_brightness = 1024,689};690 691static const struct mt6323_data mt6323_pdata = {692 .regs = &mt6323_registers,693 .spec = &mt6323_spec,694};695 696static const struct mt6323_data mt6331_pdata = {697 .regs = &mt6331_registers,698 .spec = &mt6323_spec,699};700 701static const struct mt6323_data mt6332_pdata = {702 .regs = &mt6332_registers,703 .spec = &mt6332_spec,704};705 706static const struct of_device_id mt6323_led_dt_match[] = {707 { .compatible = "mediatek,mt6323-led", .data = &mt6323_pdata},708 { .compatible = "mediatek,mt6331-led", .data = &mt6331_pdata },709 { .compatible = "mediatek,mt6332-led", .data = &mt6332_pdata },710 {},711};712MODULE_DEVICE_TABLE(of, mt6323_led_dt_match);713 714static struct platform_driver mt6323_led_driver = {715 .probe = mt6323_led_probe,716 .remove_new = mt6323_led_remove,717 .driver = {718 .name = "mt6323-led",719 .of_match_table = mt6323_led_dt_match,720 },721};722 723module_platform_driver(mt6323_led_driver);724 725MODULE_DESCRIPTION("LED driver for Mediatek MT6323 PMIC");726MODULE_AUTHOR("Sean Wang <sean.wang@mediatek.com>");727MODULE_LICENSE("GPL");728