diff --git a/Documentation/devicetree/bindings/leds/rohm,bd65b60.txt b/Documentation/devicetree/bindings/leds/rohm,bd65b60.txt new file mode 100644 index 00000000000000..8b8b962f5f19df --- /dev/null +++ b/Documentation/devicetree/bindings/leds/rohm,bd65b60.txt @@ -0,0 +1,47 @@ +ROHM BD65B60 device tree bindings + +BD65B60 is a white LED driver IC that integrates PWM +step-up DC/DC converter with boost-capability of up to +maximum 28.5V and current driver with drive capability of up +to 25mA(Typ.) maximum setting. Precise brightness can be +controlled at wide ranges through the external PWM pulse +input. + +Ranges below noted as [a, b] are closed ranges between a and b, i.e. a +and b are included in the range. + +Please also see common.txt in the same directory. + + +Required properties +=================== + +compatible : Must be "rohm,bd65b60". +reg : The I2C address of the device. Typically 0x64. +linux,name : Sysfs name. + +Optional properties +=========================================== + +linux,default-trigger : Trigger +rohm,no-reset : Don't trigger software reset during initalisation. +rohm,default-on : Set LED to on state by default. +rohm,led1-used : LED1 is being used. +rohm,led2-used : LED2 is being used. +rohm,init-level : Default brightness [0, 255]. +rohm,ovp-val : Over Voltage Protection Detect Voltage (can be: 0x0, 0x8 or 0x10) + +Example +======= + + wled: bd65b60@64 { + compatible = "rohm,bd65b60"; + reg = <0x64>; + linux,name = "wled:backlight"; + linux,default-trigger = "bkl-trigger"; + rohm,no-reset; + rohm,led1-used; + pinctrl-names = "default"; + pinctrl-0 = <&backlight_reset_default>; + }; + diff --git a/arch/arm64/boot/dts/qcom/msm8916-motorola-surnia.dts b/arch/arm64/boot/dts/qcom/msm8916-motorola-surnia.dts index ac51a491b56630..b12805a953937f 100644 --- a/arch/arm64/boot/dts/qcom/msm8916-motorola-surnia.dts +++ b/arch/arm64/boot/dts/qcom/msm8916-motorola-surnia.dts @@ -57,6 +57,28 @@ pinctrl-1 = <&usb_id_sleep>; pinctrl-names = "default", "sleep"; }; + + backlight: panel-backlight { + compatible = "led-backlight"; + leds = <&wled>; + default-brightness-level = <192>; + }; +}; + +&blsp_i2c1 { + status = "okay"; + + wled: bd65b60@64 { + compatible = "rohm,bd65b60"; + reg = <0x64>; + linux,name = "wled:backlight"; + linux,default-trigger = "bkl-trigger"; + rohm,no-reset; + rohm,led1-used; + pinctrl-names = "default"; + pinctrl-0 = <&backlight_reset_default>; + }; + }; &blsp_i2c2 { @@ -124,7 +146,7 @@ panel@0 { compatible = "motorola,surnia-panel"; reg = <0>; - + backlight = <&backlight>; power-supply = <&pm8916_l17>; reset-gpios = <&msmgpio 78 GPIO_ACTIVE_LOW>; @@ -320,6 +342,14 @@ bias-pull-up; }; + backlight_reset_default: backlight-reset-default-state { + pins = "gpio36"; + function = "gpio"; + drive-strength = <2>; + bias-pull-down; + output-high; + }; + gpio_keys_default: gpio-keys-default-state { pins = "gpio107"; function = "gpio"; @@ -341,6 +371,13 @@ bias-pull-down; }; + ts_reset_default: ts-reset-default-state { + pins = "gpio20"; + function = "gpio"; + drive-strength = <2>; + bias-disable; + }; + ts_int_default: ts-int-default-state { pins = "gpio21"; function = "gpio"; diff --git a/drivers/leds/Kconfig b/drivers/leds/Kconfig index 499d0f215a8bf0..39a86f13a37029 100644 --- a/drivers/leds/Kconfig +++ b/drivers/leds/Kconfig @@ -736,6 +736,17 @@ config LEDS_BLINKM This option enables support for the BlinkM RGB LED connected through I2C. Say Y to enable support for the BlinkM LED. +config LEDS_BD65B60 + tristate "LED Driver for BD65B60" + depends on LEDS_CLASS + depends on I2C + help + If you have a LCD backlight connected to the ROHM BD65B60, + say Y here to enable this driver. + + To compile this driver as a module, choose M here: the module will + be called leds-bd65b60. + config LEDS_POWERNV tristate "LED support for PowerNV Platform" depends on LEDS_CLASS diff --git a/drivers/leds/Makefile b/drivers/leds/Makefile index 4fd2f92cd19811..ee59ab70f30c3b 100644 --- a/drivers/leds/Makefile +++ b/drivers/leds/Makefile @@ -86,6 +86,7 @@ obj-$(CONFIG_LEDS_TURRIS_OMNIA) += leds-turris-omnia.o obj-$(CONFIG_LEDS_WM831X_STATUS) += leds-wm831x-status.o obj-$(CONFIG_LEDS_WM8350) += leds-wm8350.o obj-$(CONFIG_LEDS_WRAP) += leds-wrap.o +obj-$(CONFIG_LEDS_BD65B60) += leds-bd65b60.o # LED SPI Drivers obj-$(CONFIG_LEDS_CR0014114) += leds-cr0014114.o diff --git a/drivers/leds/leds-bd65b60.c b/drivers/leds/leds-bd65b60.c new file mode 100644 index 00000000000000..626d49f67b442c --- /dev/null +++ b/drivers/leds/leds-bd65b60.c @@ -0,0 +1,318 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Simple driver for ROHM Semiconductor BD65B60GWL Backlight driver chip + * Copyright (C) 2014 ROHM Semiconductor.com + * Copyright (C) 2014 MMI + * Copyright (C) 2023 Bogdan Ionescu +*/ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define BD65B60_NAME "bd65b60" +#define BD65B60_MAX_BRIGHTNESS 255 +#define BD65B60_DEFAULT_BRIGHTNESS 255 +#define BD65B60_DEFAULT_TRIGGER "bkl-trigger" +#define BD65B60_DEFAULT_OVP_VAL BD65B60_35V_OVP + +#define REG_SFTRST 0x00 +#define REG_COMSET1 0x01 +#define REG_COMSET2 0x02 +#define REG_LEDSEL 0x03 +#define REG_ILED 0x05 +#define REG_CTRLSET 0x07 +#define REG_SLEWSET 0x08 +#define REG_PON 0x0E +#define REG_MAX REG_PON + +#define PWMEN_MASK 0x20 +#define OVP_MASK 0x18 +#define LEDSEL_MASK 0x05 + +#define INT_DEBOUNCE_MSEC 10 + +struct bd65b60_chip { + struct bd65b60_platform_data *pdata; + struct regmap *regmap; + struct device *dev; + struct workqueue_struct *ledwq; + struct work_struct ledwork; + struct led_classdev cdev; +}; + +static const struct regmap_config bd65b60_regmap = { + .reg_bits = 8, + .val_bits = 8, + .max_register = REG_MAX, +}; + +/* i2c access */ +static int bd65b60_read(struct bd65b60_chip *pchip, unsigned int reg) +{ + int rval; + unsigned int reg_val; + + rval = regmap_read(pchip->regmap, reg, ®_val); + if (rval < 0) + return rval; + return reg_val & 0xFF; +} + +static int bd65b60_write(struct bd65b60_chip *pchip, unsigned int reg, + unsigned int data) +{ + int rc; + rc = regmap_write(pchip->regmap, reg, data); + if (rc < 0) + dev_err(pchip->dev, "i2c failed to write reg %#x", reg); + return rc; +} + +static int bd65b60_update(struct bd65b60_chip *pchip, unsigned int reg, + unsigned int mask, unsigned int data) +{ + int rc; + + rc = regmap_update_bits(pchip->regmap, reg, mask, data); + if (rc < 0) + dev_err(pchip->dev, "i2c failed to update reg %#x", reg); + return rc; +} + +/* initialize chip */ +static int bd65b60_chip_init(struct i2c_client *client) +{ + int rval = 0; + struct bd65b60_chip *pchip = i2c_get_clientdata(client); + struct bd65b60_platform_data *pdata = pchip->pdata; + + if (!pdata->no_reset) + rval |= bd65b60_write(pchip, REG_SFTRST, 0x01); + /* set common settings/OVP register */ + rval |= bd65b60_update(pchip, REG_COMSET1, OVP_MASK, pdata->ovp_val); + /* set control */ + rval |= bd65b60_update(pchip, REG_LEDSEL, LEDSEL_MASK, pdata->led_sel); + /* turn on LED Driver */ + rval |= bd65b60_write(pchip, REG_PON, 0x01); + if (rval < 0) + dev_err(&client->dev, "i2c failed to access register"); + return rval; +} + +/* set brightness */ +static void bd65b60_brightness_set(struct work_struct *work) +{ + struct bd65b60_chip *pchip = + container_of(work, struct bd65b60_chip, ledwork); + unsigned int level = pchip->cdev.brightness; + static int old_level = -1; + + /* set configure pwm input on first brightness command */ + if (old_level == -1) { + dev_info(pchip->dev, "Enabling CABC"); + bd65b60_update(pchip, REG_CTRLSET, PWMEN_MASK, + BD65B60_PWM_ENABLE); + } + + if (level != old_level && old_level == 0) { + dev_info(pchip->dev, "backlight on"); + bd65b60_write(pchip, REG_PON, 0x01); + } else if (level == 0 && old_level != 0) { + dev_info(pchip->dev, "backlight off"); + } + old_level = level; + + bd65b60_write(pchip, REG_ILED, level); + if (!level) + /* turn off LED because 0 in REG_ILED = 1/256 * Imax */ + bd65b60_write(pchip, REG_PON, 0x00); +} + +static enum led_brightness bd65b60_led_get(struct led_classdev *led_cdev) +{ + struct bd65b60_chip *pchip; + int rc; + + pchip = container_of(led_cdev, struct bd65b60_chip, cdev); + rc = bd65b60_read(pchip, REG_ILED); + return led_cdev->brightness; +} + +static void bd65b60_led_set(struct led_classdev *led_cdev, + enum led_brightness value) +{ + struct bd65b60_chip *pchip; + + pchip = container_of(led_cdev, struct bd65b60_chip, cdev); + if (value < LED_OFF) { + dev_err(pchip->dev, "Invalid brightness value"); + return; + } + + if (value > led_cdev->max_brightness) + value = led_cdev->max_brightness; + + led_cdev->brightness = value; + schedule_work(&pchip->ledwork); +} + +#ifdef CONFIG_OF +static int bd65b60_dt_init(struct i2c_client *client, + struct bd65b60_platform_data *pdata) +{ + struct device_node *np = client->dev.of_node; + int rc; + + rc = of_property_read_string(np, "linux,name", &pdata->name); + if (rc) { + dev_err(&client->dev, "No linux name provided"); + return rc; + } + + pdata->trigger = BD65B60_DEFAULT_TRIGGER; + of_property_read_string(np, "linux,default-trigger", &pdata->trigger); + + if (of_property_read_bool(np, "rohm,led1-used")) + pdata->led_sel |= BD65B60_LED1SEL; + if (of_property_read_bool(np, "rohm,led2-used")) + pdata->led_sel |= BD65B60_LED2SEL; + + pdata->no_reset = of_property_read_bool(np, "rohm,no-reset"); + + pdata->default_on = of_property_read_bool(np, "rohm,default-on"); + pdata->init_level = BD65B60_DEFAULT_BRIGHTNESS; + if (pdata->default_on) + of_property_read_u32(np, "rohm,init-level", &pdata->init_level); + + pdata->ovp_val = BD65B60_DEFAULT_OVP_VAL; + of_property_read_u32(np, "rohm,ovp-val", &pdata->ovp_val); + + return 0; +} + +static const struct of_device_id of_bd65b60_leds_match[] = { + { + .compatible = "rohm,bd65b60", + }, + { /* sentinel */ }, +}; + +#else +static int bd65b60_dt_init(struct i2c_client *client, + struct bd65b60_platform_data *pdata) +{ + return ERR_PTR(-ENODEV); +} + +static const struct of_device_id of_bd65b60_leds_match; + +#endif + +static int bd65b60_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + struct device_node *np = client->dev.of_node; + struct bd65b60_platform_data *pdata = dev_get_platdata(&client->dev); + struct bd65b60_chip *pchip; + struct led_init_data init_data = {}; + int rc; + + if (!i2c_check_functionality(client->adapter, I2C_FUNC_I2C)) { + dev_err(&client->dev, "fail : i2c functionality check"); + return -EOPNOTSUPP; + } + + pchip = devm_kzalloc(&client->dev, sizeof(struct bd65b60_chip), + GFP_KERNEL); + if (!pchip) + return -ENOMEM; + + if (!pdata) { + pdata = devm_kzalloc(&client->dev, + sizeof(struct bd65b60_platform_data), + GFP_KERNEL); + if (!pdata) + return -ENOMEM; + + rc = bd65b60_dt_init(client, pdata); + if (rc) + return rc; + } + + i2c_set_clientdata(client, pchip); + pchip->pdata = pdata; + pchip->dev = &client->dev; + + pchip->regmap = devm_regmap_init_i2c(client, &bd65b60_regmap); + if (IS_ERR(pchip->regmap)) { + rc = PTR_ERR(pchip->regmap); + dev_err(&client->dev, "fail : allocate reg. map: %d", rc); + return rc; + } + + /* chip initialize */ + rc = bd65b60_chip_init(client); + if (rc < 0) { + dev_err(&client->dev, "fail : init chip"); + return rc; + } + + /* led classdev register */ + pchip->cdev.brightness_set = bd65b60_led_set; + pchip->cdev.brightness_get = bd65b60_led_get; + pchip->cdev.max_brightness = BD65B60_MAX_BRIGHTNESS; + pchip->cdev.name = pdata->name; + pchip->cdev.default_trigger = pdata->trigger; + INIT_WORK(&pchip->ledwork, bd65b60_brightness_set); + + init_data.fwnode = &np->fwnode; + rc = devm_led_classdev_register_ext(&client->dev, &pchip->cdev, + &init_data); + if (rc) { + dev_err(&client->dev, "unable to register led rc=%d", rc); + return rc; + } + + if (pdata->default_on) + bd65b60_led_set(&pchip->cdev, pdata->init_level); + + return 0; +} + +static void bd65b60_remove(struct i2c_client *client) +{ + struct bd65b60_chip *pchip = i2c_get_clientdata(client); + + bd65b60_write(pchip, REG_PON, 0); + led_classdev_unregister(&pchip->cdev); +} + +static const struct i2c_device_id bd65b60_id[] = { { BD65B60_NAME, 0 }, {} }; + +MODULE_DEVICE_TABLE(i2c, bd65b60_id); +MODULE_DEVICE_TABLE(of, of_bd65b60_leds_match); + +static struct i2c_driver bd65b60_i2c_driver = { + .driver = { + .name = BD65B60_NAME, + .owner = THIS_MODULE, + .of_match_table = of_match_ptr(of_bd65b60_leds_match), + }, + .probe = bd65b60_probe, + .remove = bd65b60_remove, + .id_table = bd65b60_id, +}; + +module_i2c_driver(bd65b60_i2c_driver); + +MODULE_DESCRIPTION("ROHM Semiconductor Backlight driver for bd65b60"); +MODULE_LICENSE("GPL"); diff --git a/include/linux/platform_data/leds-bd65b60.h b/include/linux/platform_data/leds-bd65b60.h new file mode 100644 index 00000000000000..89ed7659c148db --- /dev/null +++ b/include/linux/platform_data/leds-bd65b60.h @@ -0,0 +1,49 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Simple driver for ROHM Semiconductor BD65B60GWL Backlight driver chip + * Copyright (C) 2014 ROHM Semiconductor.com + * Copyright (C) 2014 MMI + * Copyright (C) 2023 Bogdan Ionescu +*/ + +#ifndef __BD65B60_H__ +#define __BD65B60_H__ + +enum bd65b60_ovp { + BD65B60_25V_OVP = 0x00, + BD65B60_30V_OVP = 0x08, + BD65B60_35V_OVP = 0x10, +}; + +enum bd65b60_ledsel { + BD65B60_DISABLE = 0x00, + BD65B60_LED1SEL = 0x01, + BD65B60_LED2SEL = 0x04, + BD65B60_LED12SEL = 0x05, +}; + +enum bd65b60_pwm_ctrl { + BD65B60_PWM_DISABLE = 0x00, + BD65B60_PWM_ENABLE = 0x20, +}; + +/* + *@init_level : led a init brightness. 4~255 + *@no_reset : disable reset on init enable/disable + *@led_sel : led rail enable/disable + *@ovp_val : LED OVP Settings + *@name : device name + *@trigger : trigger + *@default_on : default state on enable/disable + */ +struct bd65b60_platform_data { + int init_level; + bool no_reset; + enum bd65b60_ledsel led_sel; + enum bd65b60_ovp ovp_val; + const char *name; + const char *trigger; + bool default_on; +}; + +#endif