diff mbox series

[2/2,v3] leds: rt8515: Add Richtek RT8515 LED driver

Message ID 20201111011417.2275501-2-linus.walleij@linaro.org
State New
Headers show
Series [1/2,v3] dt-bindings: leds: Add DT binding for Richtek RT8515 | expand

Commit Message

Linus Walleij Nov. 11, 2020, 1:14 a.m. UTC
This adds a driver for the Richtek RT8515 dual channel
torch/flash white LED driver.

This LED driver is found in some mobile phones from
Samsung such as the GT-S7710 and GT-I8190.

A V4L interface is added.

I do not have any datsheet for the Richtek RT8515.
In the outoftree code that exists for example for Asus
Zenfone the intensity is set to min/max in percent
(0-100%) so the numerals 1-100 step 1 so the same as
the brightness. I do not know the actuall current
this results in.

Cc: Sakari Ailus <sakari.ailus@iki.fi>
Cc: newbytee@protonmail.com
Cc: Stephan Gerhold <stephan@gerhold.net>
Signed-off-by: Linus Walleij <linus.walleij@linaro.org>

---
Changelog v2->v3:
- Expand commit message.
- Add Sakari to Cc.
- Resend.
ChangeLog v1->v2:
- Break out routine to bitbang the brightness on a
  GPIO pin.
- Do not hardcode the LED name so that the framework
  can name it from DT properties.
---
 drivers/leds/Kconfig       |  11 ++
 drivers/leds/Makefile      |   1 +
 drivers/leds/leds-rt8515.c | 303 +++++++++++++++++++++++++++++++++++++
 3 files changed, 315 insertions(+)
 create mode 100644 drivers/leds/leds-rt8515.c

-- 
2.26.2

Comments

Pavel Machek Nov. 11, 2020, 7:59 a.m. UTC | #1
Hi!

> This adds a driver for the Richtek RT8515 dual channel

> torch/flash white LED driver.

> 

> This LED driver is found in some mobile phones from

> Samsung such as the GT-S7710 and GT-I8190.


Cc: phone-devel@vger.

>  drivers/leds/Kconfig       |  11 ++

>  drivers/leds/Makefile      |   1 +

>  drivers/leds/leds-rt8515.c | 303 +++++++++++++++++++++++++++++++++++++


Let's put this into drivers/leds/flash. You may need to create it.

> +// SPDX-License-Identifier: GPL-2.0-only


GPL-2+ would be nicer if you can.

> +struct rt8515 {

> +	struct led_classdev_flash fled;

> +	struct v4l2_flash *v4l2_flash;

> +	struct mutex lock;

> +	struct regulator *reg;

> +	struct gpio_desc *ent;

> +	struct gpio_desc *enf;


-> enable_torch or at least en_torch / en_flash?

Otherwise it looks good.

Best regards,
							Pavel

-- 
http://www.livejournal.com/~pavelmachek
Sakari Ailus Nov. 11, 2020, 11:38 a.m. UTC | #2
Hi Linus,

On Wed, Nov 11, 2020 at 02:14:17AM +0100, Linus Walleij wrote:
> This adds a driver for the Richtek RT8515 dual channel

> torch/flash white LED driver.

> 

> This LED driver is found in some mobile phones from

> Samsung such as the GT-S7710 and GT-I8190.

> 

> A V4L interface is added.

> 

> I do not have any datsheet for the Richtek RT8515.

> In the outoftree code that exists for example for Asus

> Zenfone the intensity is set to min/max in percent

> (0-100%) so the numerals 1-100 step 1 so the same as

> the brightness. I do not know the actuall current

> this results in.

> 

> Cc: Sakari Ailus <sakari.ailus@iki.fi>

> Cc: newbytee@protonmail.com

> Cc: Stephan Gerhold <stephan@gerhold.net>

> Signed-off-by: Linus Walleij <linus.walleij@linaro.org>

> ---

> Changelog v2->v3:

> - Expand commit message.

> - Add Sakari to Cc.

> - Resend.

> ChangeLog v1->v2:

> - Break out routine to bitbang the brightness on a

>   GPIO pin.

> - Do not hardcode the LED name so that the framework

>   can name it from DT properties.

> ---

>  drivers/leds/Kconfig       |  11 ++

>  drivers/leds/Makefile      |   1 +

>  drivers/leds/leds-rt8515.c | 303 +++++++++++++++++++++++++++++++++++++

>  3 files changed, 315 insertions(+)

>  create mode 100644 drivers/leds/leds-rt8515.c

> 

> diff --git a/drivers/leds/Kconfig b/drivers/leds/Kconfig

> index 849d3c5f908e..df632608d732 100644

> --- a/drivers/leds/Kconfig

> +++ b/drivers/leds/Kconfig

> @@ -913,6 +913,17 @@ config LEDS_IP30

>  	  To compile this driver as a module, choose M here: the module

>  	  will be called leds-ip30.

>  

> +config LEDS_RT8515

> +	tristate "LED support for Richtek RT8515 flash/torch LED"

> +	depends on LEDS_CLASS_FLASH

> +	depends on GPIOLIB

> +	help

> +	  This option enables support for the Richtek RT8515 flash

> +	  and torch LEDs found on some mobile phones.

> +

> +	  To compile this driver as a module, choose M here: the module

> +	  will be called leds-rt8515.

> +

>  config LEDS_SGM3140

>  	tristate "LED support for the SGM3140"

>  	depends on LEDS_CLASS_FLASH

> diff --git a/drivers/leds/Makefile b/drivers/leds/Makefile

> index 73e603e1727e..aa6b2a19e051 100644

> --- a/drivers/leds/Makefile

> +++ b/drivers/leds/Makefile

> @@ -80,6 +80,7 @@ obj-$(CONFIG_LEDS_PM8058)		+= leds-pm8058.o

>  obj-$(CONFIG_LEDS_POWERNV)		+= leds-powernv.o

>  obj-$(CONFIG_LEDS_PWM)			+= leds-pwm.o

>  obj-$(CONFIG_LEDS_REGULATOR)		+= leds-regulator.o

> +obj-$(CONFIG_LEDS_RT8515)		+= leds-rt8515.o

>  obj-$(CONFIG_LEDS_S3C24XX)		+= leds-s3c24xx.o

>  obj-$(CONFIG_LEDS_SC27XX_BLTC)		+= leds-sc27xx-bltc.o

>  obj-$(CONFIG_LEDS_SGM3140)		+= leds-sgm3140.o

> diff --git a/drivers/leds/leds-rt8515.c b/drivers/leds/leds-rt8515.c

> new file mode 100644

> index 000000000000..a87019b7eebe

> --- /dev/null

> +++ b/drivers/leds/leds-rt8515.c

> @@ -0,0 +1,303 @@

> +// SPDX-License-Identifier: GPL-2.0-only

> +/*

> + * LED driver for Richtek RT8515 flash/torch white LEDs

> + * found on some Samsung mobile phones.

> + * This is a 1.5A Boost dual channel driver produced around 2011.

> + *

> + * Linus Walleij <linus.walleij@linaro.org>

> + */

> +#include <linux/delay.h>

> +#include <linux/err.h>

> +#include <linux/gpio/consumer.h>

> +#include <linux/led-class-flash.h>

> +#include <linux/module.h>

> +#include <linux/of.h>

> +#include <linux/platform_device.h>

> +#include <linux/regulator/consumer.h>

> +

> +#include <media/v4l2-flash-led-class.h>

> +

> +#define RT8515_FLASH_MAX 16


This is unused.

> +#define RT8515_TORCH_MAX 100

> +

> +#define RT8515_TIMEOUT_DEFAULT		250000U /* 250ms */

> +#define RT8515_MAX_TIMEOUT_DEFAULT	300000U /* 300ms */

> +

> +struct rt8515 {

> +	struct led_classdev_flash fled;

> +	struct v4l2_flash *v4l2_flash;

> +	struct mutex lock;

> +	struct regulator *reg;

> +	struct gpio_desc *ent;

> +	struct gpio_desc *enf;

> +	struct timer_list powerdown_timer;

> +	u32 max_timeout; /* Flash max timeout */

> +};

> +

> +static struct rt8515 *to_rt8515(struct led_classdev_flash *fled)

> +{

> +	return container_of(fled, struct rt8515, fled);

> +}

> +

> +static void rt8515_gpio_brightness_commit(struct gpio_desc *gpiod,

> +					  int brightness)

> +{

> +	int i;

> +

> +	/*

> +	 * Toggling a GPIO line with a small delay increases the

> +	 * brightness one step at a time.

> +	 */


Wow.

> +	for (i = 0; i < brightness; i++) {

> +		gpiod_set_value(gpiod, 0);

> +		udelay(1);

> +		gpiod_set_value(gpiod, 1);

> +		udelay(1);

> +	}

> +}

> +

> +/* This is setting the torch light level */

> +static int rt8515_led_brightness_set(struct led_classdev *led,

> +				     enum led_brightness brightness)

> +{

> +	struct led_classdev_flash *fled = lcdev_to_flcdev(led);

> +	struct rt8515 *rt = to_rt8515(fled);

> +

> +	mutex_lock(&rt->lock);

> +

> +	if (brightness == LED_OFF) {

> +		/* Off */

> +		gpiod_set_value(rt->enf, 0);

> +		gpiod_set_value(rt->ent, 0);

> +	} else if (brightness < RT8515_TORCH_MAX) {

> +		/* Step it up to movie mode brightness using the flash pin */

> +		rt8515_gpio_brightness_commit(rt->ent, brightness);


What's the unit of brightness here? If you don't know the unit, you could
still assume something and fix it later if needed. Or the current could be
just measured.

It's usually linear but if the number of steps is small then logarithmic
scale is also not unforeseen.

> +	} else {

> +		/* Max torch brightness requested */

> +		gpiod_set_value(rt->ent, 1);


What's the current in this case? The maximum really should come from DT to
avoid frying components.

> +	}

> +

> +	mutex_unlock(&rt->lock);

> +

> +	return 0;

> +}

> +

> +static int rt8515_led_flash_strobe_set(struct led_classdev_flash *fled,

> +				       bool state)

> +{

> +	struct rt8515 *rt = to_rt8515(fled);

> +	struct led_flash_setting *timeout = &fled->timeout;

> +	int brightness = 4; /* max 16 */

> +

> +	mutex_lock(&rt->lock);

> +

> +	if (state) {

> +		/* Enable LED flash mode and set brightness */

> +		rt8515_gpio_brightness_commit(rt->enf, brightness);

> +		/* Set timeout */

> +		mod_timer(&rt->powerdown_timer,

> +			  jiffies + usecs_to_jiffies(timeout->val));

> +	} else {

> +		del_timer_sync(&rt->powerdown_timer);

> +		/* Turn the LED off */

> +		gpiod_set_value(rt->enf, 0);

> +		gpiod_set_value(rt->ent, 0);

> +	}

> +

> +	fled->led_cdev.brightness = LED_OFF;

> +	/* After this the torch LED will be disabled */

> +

> +	mutex_unlock(&rt->lock);

> +

> +	return 0;

> +}

> +

> +static int rt8515_led_flash_strobe_get(struct led_classdev_flash *fled,

> +				       bool *state)

> +{

> +	struct rt8515 *rt = to_rt8515(fled);

> +

> +	*state = timer_pending(&rt->powerdown_timer);

> +

> +	return 0;

> +}

> +

> +static int rt8515_led_flash_timeout_set(struct led_classdev_flash *fled,

> +					u32 timeout)

> +{

> +	/* The timeout is stored in the led-class-flash core */

> +	return 0;

> +}

> +

> +static const struct led_flash_ops rt8515_flash_ops = {

> +	.strobe_set = rt8515_led_flash_strobe_set,

> +	.strobe_get = rt8515_led_flash_strobe_get,

> +	.timeout_set = rt8515_led_flash_timeout_set,

> +};

> +

> +static void rt8515_powerdown_timer(struct timer_list *t)

> +{

> +	struct rt8515 *rt = from_timer(rt, t, powerdown_timer);

> +

> +	/* Turn the LED off */

> +	gpiod_set_value(rt->enf, 0);

> +	gpiod_set_value(rt->ent, 0);

> +}

> +

> +static void rt8515_init_flash_timeout(struct rt8515 *rt)

> +{

> +	struct led_classdev_flash *fled = &rt->fled;

> +	struct led_flash_setting *s;

> +

> +	/* Init flash timeout setting */

> +	s = &fled->timeout;

> +	s->min = 1;

> +	s->max = rt->max_timeout;

> +	s->step = 1;

> +	/*

> +	 * Set default timeout to RT8515_DEFAULT_TIMEOUT except if

> +	 * max_timeout from DT is lower.

> +	 */

> +	s->val = min(rt->max_timeout, RT8515_TIMEOUT_DEFAULT);

> +}

> +

> +#if IS_ENABLED(CONFIG_V4L2_FLASH_LED_CLASS)

> +/* Configure the V2L2 flash subdevice */

> +static void rt8515_init_v4l2_flash_config(struct rt8515 *rt,

> +					  struct v4l2_flash_config *v4l2_sd_cfg)

> +{

> +	struct led_classdev *led = &rt->fled.led_cdev;

> +	struct led_flash_setting *s;

> +

> +	strscpy(v4l2_sd_cfg->dev_name, led->dev->kobj.name,

> +		sizeof(v4l2_sd_cfg->dev_name));

> +

> +	/* Init flash intensity setting */

> +	s = &v4l2_sd_cfg->intensity;

> +	s->min = 0;

> +	s->max = rt->fled.led_cdev.max_brightness;

> +	s->step = 1;

> +	s->val = s->max;

> +}

> +

> +#else

> +static void rt8515_init_v4l2_flash_config(struct rt8515 *rt,

> +					  struct v4l2_flash_config *v4l2_sd_cfg)

> +{

> +}

> +#endif

> +

> +static int rt8515_probe(struct platform_device *pdev)

> +{

> +	struct device *dev = &pdev->dev;

> +	struct fwnode_handle *child;

> +	struct rt8515 *rt;

> +	struct led_classdev *led;

> +	struct led_classdev_flash *fled;

> +	struct led_init_data init_data = {};

> +	struct v4l2_flash_config v4l2_sd_cfg = {};

> +	int ret;

> +

> +	rt = devm_kzalloc(dev, sizeof(*rt), GFP_KERNEL);

> +	if (!rt)

> +		return -ENOMEM;

> +

> +	fled = &rt->fled;

> +	led = &fled->led_cdev;

> +

> +	/* ENF - Enable Flash line */

> +	rt->enf = devm_gpiod_get(dev, "enf", GPIOD_OUT_LOW);

> +	if (IS_ERR(rt->enf)) {

> +		dev_err(dev, "cannot get ENF (enable flash) GPIO\n");

> +		return PTR_ERR(rt->enf);

> +	}

> +

> +	/* ENT - Enable Torch line */

> +	rt->ent = devm_gpiod_get(dev, "ent", GPIOD_OUT_LOW);

> +	if (IS_ERR(rt->ent)) {

> +		dev_err(dev, "cannot get ENT (enable torch) GPIO\n");

> +		return PTR_ERR(rt->ent);

> +	}

> +

> +	child = fwnode_get_next_available_child_node(dev->fwnode, NULL);

> +	if (!child) {

> +		dev_err(dev,

> +			"No fwnode child node found for connected LED.\n");

> +		return -EINVAL;

> +	}

> +	init_data.fwnode = child;

> +

> +	ret = fwnode_property_read_u32(child, "flash-max-timeout-us",

> +				       &rt->max_timeout);

> +	if (ret) {

> +		rt->max_timeout = RT8515_MAX_TIMEOUT_DEFAULT;

> +		dev_warn(dev,

> +			 "flash-max-timeout-us property missing\n");

> +	}

> +	timer_setup(&rt->powerdown_timer, rt8515_powerdown_timer, 0);

> +	rt8515_init_flash_timeout(rt);

> +

> +	fled->ops = &rt8515_flash_ops;

> +

> +	led->max_brightness = RT8515_TORCH_MAX;

> +	led->brightness_set_blocking = rt8515_led_brightness_set;

> +	led->flags |= LED_CORE_SUSPENDRESUME | LED_DEV_CAP_FLASH;

> +

> +	mutex_init(&rt->lock);

> +

> +	platform_set_drvdata(pdev, rt);

> +

> +	ret = devm_led_classdev_flash_register_ext(dev, fled, &init_data);

> +	if (ret) {

> +		dev_err(dev, "can't register LED %s\n", led->name);

> +		mutex_destroy(&rt->lock);

> +		return ret;

> +	}

> +

> +	rt8515_init_v4l2_flash_config(rt, &v4l2_sd_cfg);

> +

> +	/* Create a V4L2 Flash device if V4L2 flash is enabled */

> +	rt->v4l2_flash = v4l2_flash_init(dev, child, fled, NULL, &v4l2_sd_cfg);

> +	if (IS_ERR(rt->v4l2_flash)) {

> +		ret = PTR_ERR(rt->v4l2_flash);

> +		dev_err(dev, "failed to register V4L2 flash device (%d)\n",

> +			ret);

> +		/*

> +		 * Continue without the V4L2 flash

> +		 * (we still have the classdev)

> +		 */

> +	}

> +

> +	return 0;

> +}

> +

> +static int rt8515_remove(struct platform_device *pdev)

> +{

> +	struct rt8515 *rt = platform_get_drvdata(pdev);

> +

> +	v4l2_flash_release(rt->v4l2_flash);

> +	del_timer_sync(&rt->powerdown_timer);

> +	mutex_destroy(&rt->lock);

> +

> +	return 0;

> +}

> +

> +static const struct of_device_id rt8515_match[] = {

> +	{ .compatible = "richtek,rt8515", },

> +	{ /* sentinel */ },

> +};

> +MODULE_DEVICE_TABLE(of, rt8515_match);

> +

> +static struct platform_driver rt8515_driver = {

> +	.driver = {

> +		.name  = "rt8515",

> +		.of_match_table = rt8515_match,

> +	},

> +	.probe  = rt8515_probe,

> +	.remove = rt8515_remove,

> +};

> +module_platform_driver(rt8515_driver);

> +

> +MODULE_AUTHOR("Linus Walleij <linus.walleij@linaro.org>");

> +MODULE_DESCRIPTION("Richtek RT8515 LED driver");

> +MODULE_LICENSE("GPL v2");


-- 
Kind regards,

Sakari Ailus
Linus Walleij Nov. 11, 2020, 4:34 p.m. UTC | #3
On Wed, Nov 11, 2020 at 12:40 PM Sakari Ailus <sakari.ailus@iki.fi> wrote:
> On Wed, Nov 11, 2020 at 02:14:17AM +0100, Linus Walleij wrote:


> > +     } else if (brightness < RT8515_TORCH_MAX) {

> > +             /* Step it up to movie mode brightness using the flash pin */

> > +             rt8515_gpio_brightness_commit(rt->ent, brightness);

>

> What's the unit of brightness here? If you don't know the unit, you could

> still assume something and fix it later if needed. Or the current could be

> just measured.

>

> It's usually linear but if the number of steps is small then logarithmic

> scale is also not unforeseen.


I will try to come up with something...

> > +     } else {

> > +             /* Max torch brightness requested */

> > +             gpiod_set_value(rt->ent, 1);

>

> What's the current in this case? The maximum really should come from DT to

> avoid frying components.


The way I understand it is that this component contains its own
current regulation electronic. You request a brightness
between 1-100 and it will support this range (no external
current boost). And as a user that is "all you need to know".

Isn't this problem more prevalent when you have some kind of
external current-regulator that you need to program?

This component draws its power directly from VBAT (the main
battery) so regulating how much of that it takes is up to the
component.

I could think of the component brightness being a problem if
the flash is embedded in some kind of plastic that cannot
take the heat though, but I haven't seen any code trying to
hold it down for this reason. I suppose the component
datasheet (that I don't have) specifies all these things...

Yours,
Linus Walleij
Sakari Ailus Nov. 11, 2020, 4:55 p.m. UTC | #4
Hi Linus,

On Wed, Nov 11, 2020 at 05:34:58PM +0100, Linus Walleij wrote:
> On Wed, Nov 11, 2020 at 12:40 PM Sakari Ailus <sakari.ailus@iki.fi> wrote:

> > On Wed, Nov 11, 2020 at 02:14:17AM +0100, Linus Walleij wrote:

> 

> > > +     } else if (brightness < RT8515_TORCH_MAX) {

> > > +             /* Step it up to movie mode brightness using the flash pin */

> > > +             rt8515_gpio_brightness_commit(rt->ent, brightness);

> >

> > What's the unit of brightness here? If you don't know the unit, you could

> > still assume something and fix it later if needed. Or the current could be

> > just measured.

> >

> > It's usually linear but if the number of steps is small then logarithmic

> > scale is also not unforeseen.

> 

> I will try to come up with something...

> 

> > > +     } else {

> > > +             /* Max torch brightness requested */

> > > +             gpiod_set_value(rt->ent, 1);

> >

> > What's the current in this case? The maximum really should come from DT to

> > avoid frying components.

> 

> The way I understand it is that this component contains its own

> current regulation electronic. You request a brightness

> between 1-100 and it will support this range (no external

> current boost). And as a user that is "all you need to know".

> 

> Isn't this problem more prevalent when you have some kind of

> external current-regulator that you need to program?

> 

> This component draws its power directly from VBAT (the main

> battery) so regulating how much of that it takes is up to the

> component.

> 

> I could think of the component brightness being a problem if

> the flash is embedded in some kind of plastic that cannot

> take the heat though, but I haven't seen any code trying to

> hold it down for this reason. I suppose the component

> datasheet (that I don't have) specifies all these things...


The LED is different from the LED driver. If you happen to have a LED with
smaller maximum current than the LED driver can provide, the software has
to limit the current the driver provides, or hardware damage will result.

This is why virtually all flash LED drivers have these properties.

I guess you could use the maximum as it is known the driver uses the
maximum current on those devices? How about the torch mode, does that use
maximum as well?

A (safe) way forward now, without knowing the current in various
configurations, could be to document the said properties in DT bindings,
but the driver would only work if it gets the maximum values from the DT.
Or use the lowest setting otherwise.

-- 
Kind regards,

Sakari Ailus
Linus Walleij Nov. 11, 2020, 10:07 p.m. UTC | #5
On Wed, Nov 11, 2020 at 5:56 PM Sakari Ailus <sakari.ailus@iki.fi> wrote:
> On Wed, Nov 11, 2020 at 05:34:58PM +0100, Linus Walleij wrote:


> > The way I understand it is that this component contains its own

> > current regulation electronic. You request a brightness

> > between 1-100 and it will support this range (no external

> > current boost). And as a user that is "all you need to know".

> >

> > Isn't this problem more prevalent when you have some kind of

> > external current-regulator that you need to program?

> >

> > This component draws its power directly from VBAT (the main

> > battery) so regulating how much of that it takes is up to the

> > component.

> >

> > I could think of the component brightness being a problem if

> > the flash is embedded in some kind of plastic that cannot

> > take the heat though, but I haven't seen any code trying to

> > hold it down for this reason. I suppose the component

> > datasheet (that I don't have) specifies all these things...

>

> The LED is different from the LED driver. If you happen to have a LED with

> smaller maximum current than the LED driver can provide, the software has

> to limit the current the driver provides, or hardware damage will result.

>

> This is why virtually all flash LED drivers have these properties.


Hm you're right of course.

I did some research, the flash driver in the RT8515
appears to be somewhat clever.

Here is a schematic picture from the LG P970 service
manual where you can see the connections from the RT8515
to the LED:
https://dflund.se/~triad/images/rt8515.jpg

On this image you can see that there are two resistors connected
from the pins "RFS" and "RTS" to ground.

RFS (resistance flash setting?) is 20 kOhm
RTS (resistance torch setting?) is 39 kOhm

Some sleuthing finds us the RT9387A which we have a datasheet for:
https://static5.arrow.com/pdfs/2014/7/27/8/21/12/794/rtt_/manual/94download_ds.jspprt9387a.jspprt9387a.pdf
This apparently works the same way so now we have a
RT9387A driver as well.

The two resistances control the max current for flash
and torch, with I = 5500 / R, up to 700 mA.
For 20 and 39 kOhm this means

ImaxFlash = 275 mA
ImaxTorch = 141 mA

So the max current is actually hardwired into the
circuit.

The setting of brightness is done with the pulse train,
but it is a PWM dimmer setting on top of the max
current.

So I'll just put in these max currents (assuming they
are using the same equation).

Yours,
Linus Walleij
Pavel Machek Nov. 25, 2020, 7:35 p.m. UTC | #6
Hi!

> Here is a schematic picture from the LG P970 service

> manual where you can see the connections from the RT8515

> to the LED:

> https://dflund.se/~triad/images/rt8515.jpg

> 

> On this image you can see that there are two resistors connected

> from the pins "RFS" and "RTS" to ground.

> 

> RFS (resistance flash setting?) is 20 kOhm

> RTS (resistance torch setting?) is 39 kOhm

> 

> Some sleuthing finds us the RT9387A which we have a datasheet for:

> https://static5.arrow.com/pdfs/2014/7/27/8/21/12/794/rtt_/manual/94download_ds.jspprt9387a.jspprt9387a.pdf

> This apparently works the same way so now we have a

> RT9387A driver as well.


These links and RT9387A would be useful to mention in a comment in a
driver...

Best regards,
								Pavel

-- 
http://www.livejournal.com/~pavelmachek
diff mbox series

Patch

diff --git a/drivers/leds/Kconfig b/drivers/leds/Kconfig
index 849d3c5f908e..df632608d732 100644
--- a/drivers/leds/Kconfig
+++ b/drivers/leds/Kconfig
@@ -913,6 +913,17 @@  config LEDS_IP30
 	  To compile this driver as a module, choose M here: the module
 	  will be called leds-ip30.
 
+config LEDS_RT8515
+	tristate "LED support for Richtek RT8515 flash/torch LED"
+	depends on LEDS_CLASS_FLASH
+	depends on GPIOLIB
+	help
+	  This option enables support for the Richtek RT8515 flash
+	  and torch LEDs found on some mobile phones.
+
+	  To compile this driver as a module, choose M here: the module
+	  will be called leds-rt8515.
+
 config LEDS_SGM3140
 	tristate "LED support for the SGM3140"
 	depends on LEDS_CLASS_FLASH
diff --git a/drivers/leds/Makefile b/drivers/leds/Makefile
index 73e603e1727e..aa6b2a19e051 100644
--- a/drivers/leds/Makefile
+++ b/drivers/leds/Makefile
@@ -80,6 +80,7 @@  obj-$(CONFIG_LEDS_PM8058)		+= leds-pm8058.o
 obj-$(CONFIG_LEDS_POWERNV)		+= leds-powernv.o
 obj-$(CONFIG_LEDS_PWM)			+= leds-pwm.o
 obj-$(CONFIG_LEDS_REGULATOR)		+= leds-regulator.o
+obj-$(CONFIG_LEDS_RT8515)		+= leds-rt8515.o
 obj-$(CONFIG_LEDS_S3C24XX)		+= leds-s3c24xx.o
 obj-$(CONFIG_LEDS_SC27XX_BLTC)		+= leds-sc27xx-bltc.o
 obj-$(CONFIG_LEDS_SGM3140)		+= leds-sgm3140.o
diff --git a/drivers/leds/leds-rt8515.c b/drivers/leds/leds-rt8515.c
new file mode 100644
index 000000000000..a87019b7eebe
--- /dev/null
+++ b/drivers/leds/leds-rt8515.c
@@ -0,0 +1,303 @@ 
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * LED driver for Richtek RT8515 flash/torch white LEDs
+ * found on some Samsung mobile phones.
+ * This is a 1.5A Boost dual channel driver produced around 2011.
+ *
+ * Linus Walleij <linus.walleij@linaro.org>
+ */
+#include <linux/delay.h>
+#include <linux/err.h>
+#include <linux/gpio/consumer.h>
+#include <linux/led-class-flash.h>
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/platform_device.h>
+#include <linux/regulator/consumer.h>
+
+#include <media/v4l2-flash-led-class.h>
+
+#define RT8515_FLASH_MAX 16
+#define RT8515_TORCH_MAX 100
+
+#define RT8515_TIMEOUT_DEFAULT		250000U /* 250ms */
+#define RT8515_MAX_TIMEOUT_DEFAULT	300000U /* 300ms */
+
+struct rt8515 {
+	struct led_classdev_flash fled;
+	struct v4l2_flash *v4l2_flash;
+	struct mutex lock;
+	struct regulator *reg;
+	struct gpio_desc *ent;
+	struct gpio_desc *enf;
+	struct timer_list powerdown_timer;
+	u32 max_timeout; /* Flash max timeout */
+};
+
+static struct rt8515 *to_rt8515(struct led_classdev_flash *fled)
+{
+	return container_of(fled, struct rt8515, fled);
+}
+
+static void rt8515_gpio_brightness_commit(struct gpio_desc *gpiod,
+					  int brightness)
+{
+	int i;
+
+	/*
+	 * Toggling a GPIO line with a small delay increases the
+	 * brightness one step at a time.
+	 */
+	for (i = 0; i < brightness; i++) {
+		gpiod_set_value(gpiod, 0);
+		udelay(1);
+		gpiod_set_value(gpiod, 1);
+		udelay(1);
+	}
+}
+
+/* This is setting the torch light level */
+static int rt8515_led_brightness_set(struct led_classdev *led,
+				     enum led_brightness brightness)
+{
+	struct led_classdev_flash *fled = lcdev_to_flcdev(led);
+	struct rt8515 *rt = to_rt8515(fled);
+
+	mutex_lock(&rt->lock);
+
+	if (brightness == LED_OFF) {
+		/* Off */
+		gpiod_set_value(rt->enf, 0);
+		gpiod_set_value(rt->ent, 0);
+	} else if (brightness < RT8515_TORCH_MAX) {
+		/* Step it up to movie mode brightness using the flash pin */
+		rt8515_gpio_brightness_commit(rt->ent, brightness);
+	} else {
+		/* Max torch brightness requested */
+		gpiod_set_value(rt->ent, 1);
+	}
+
+	mutex_unlock(&rt->lock);
+
+	return 0;
+}
+
+static int rt8515_led_flash_strobe_set(struct led_classdev_flash *fled,
+				       bool state)
+{
+	struct rt8515 *rt = to_rt8515(fled);
+	struct led_flash_setting *timeout = &fled->timeout;
+	int brightness = 4; /* max 16 */
+
+	mutex_lock(&rt->lock);
+
+	if (state) {
+		/* Enable LED flash mode and set brightness */
+		rt8515_gpio_brightness_commit(rt->enf, brightness);
+		/* Set timeout */
+		mod_timer(&rt->powerdown_timer,
+			  jiffies + usecs_to_jiffies(timeout->val));
+	} else {
+		del_timer_sync(&rt->powerdown_timer);
+		/* Turn the LED off */
+		gpiod_set_value(rt->enf, 0);
+		gpiod_set_value(rt->ent, 0);
+	}
+
+	fled->led_cdev.brightness = LED_OFF;
+	/* After this the torch LED will be disabled */
+
+	mutex_unlock(&rt->lock);
+
+	return 0;
+}
+
+static int rt8515_led_flash_strobe_get(struct led_classdev_flash *fled,
+				       bool *state)
+{
+	struct rt8515 *rt = to_rt8515(fled);
+
+	*state = timer_pending(&rt->powerdown_timer);
+
+	return 0;
+}
+
+static int rt8515_led_flash_timeout_set(struct led_classdev_flash *fled,
+					u32 timeout)
+{
+	/* The timeout is stored in the led-class-flash core */
+	return 0;
+}
+
+static const struct led_flash_ops rt8515_flash_ops = {
+	.strobe_set = rt8515_led_flash_strobe_set,
+	.strobe_get = rt8515_led_flash_strobe_get,
+	.timeout_set = rt8515_led_flash_timeout_set,
+};
+
+static void rt8515_powerdown_timer(struct timer_list *t)
+{
+	struct rt8515 *rt = from_timer(rt, t, powerdown_timer);
+
+	/* Turn the LED off */
+	gpiod_set_value(rt->enf, 0);
+	gpiod_set_value(rt->ent, 0);
+}
+
+static void rt8515_init_flash_timeout(struct rt8515 *rt)
+{
+	struct led_classdev_flash *fled = &rt->fled;
+	struct led_flash_setting *s;
+
+	/* Init flash timeout setting */
+	s = &fled->timeout;
+	s->min = 1;
+	s->max = rt->max_timeout;
+	s->step = 1;
+	/*
+	 * Set default timeout to RT8515_DEFAULT_TIMEOUT except if
+	 * max_timeout from DT is lower.
+	 */
+	s->val = min(rt->max_timeout, RT8515_TIMEOUT_DEFAULT);
+}
+
+#if IS_ENABLED(CONFIG_V4L2_FLASH_LED_CLASS)
+/* Configure the V2L2 flash subdevice */
+static void rt8515_init_v4l2_flash_config(struct rt8515 *rt,
+					  struct v4l2_flash_config *v4l2_sd_cfg)
+{
+	struct led_classdev *led = &rt->fled.led_cdev;
+	struct led_flash_setting *s;
+
+	strscpy(v4l2_sd_cfg->dev_name, led->dev->kobj.name,
+		sizeof(v4l2_sd_cfg->dev_name));
+
+	/* Init flash intensity setting */
+	s = &v4l2_sd_cfg->intensity;
+	s->min = 0;
+	s->max = rt->fled.led_cdev.max_brightness;
+	s->step = 1;
+	s->val = s->max;
+}
+
+#else
+static void rt8515_init_v4l2_flash_config(struct rt8515 *rt,
+					  struct v4l2_flash_config *v4l2_sd_cfg)
+{
+}
+#endif
+
+static int rt8515_probe(struct platform_device *pdev)
+{
+	struct device *dev = &pdev->dev;
+	struct fwnode_handle *child;
+	struct rt8515 *rt;
+	struct led_classdev *led;
+	struct led_classdev_flash *fled;
+	struct led_init_data init_data = {};
+	struct v4l2_flash_config v4l2_sd_cfg = {};
+	int ret;
+
+	rt = devm_kzalloc(dev, sizeof(*rt), GFP_KERNEL);
+	if (!rt)
+		return -ENOMEM;
+
+	fled = &rt->fled;
+	led = &fled->led_cdev;
+
+	/* ENF - Enable Flash line */
+	rt->enf = devm_gpiod_get(dev, "enf", GPIOD_OUT_LOW);
+	if (IS_ERR(rt->enf)) {
+		dev_err(dev, "cannot get ENF (enable flash) GPIO\n");
+		return PTR_ERR(rt->enf);
+	}
+
+	/* ENT - Enable Torch line */
+	rt->ent = devm_gpiod_get(dev, "ent", GPIOD_OUT_LOW);
+	if (IS_ERR(rt->ent)) {
+		dev_err(dev, "cannot get ENT (enable torch) GPIO\n");
+		return PTR_ERR(rt->ent);
+	}
+
+	child = fwnode_get_next_available_child_node(dev->fwnode, NULL);
+	if (!child) {
+		dev_err(dev,
+			"No fwnode child node found for connected LED.\n");
+		return -EINVAL;
+	}
+	init_data.fwnode = child;
+
+	ret = fwnode_property_read_u32(child, "flash-max-timeout-us",
+				       &rt->max_timeout);
+	if (ret) {
+		rt->max_timeout = RT8515_MAX_TIMEOUT_DEFAULT;
+		dev_warn(dev,
+			 "flash-max-timeout-us property missing\n");
+	}
+	timer_setup(&rt->powerdown_timer, rt8515_powerdown_timer, 0);
+	rt8515_init_flash_timeout(rt);
+
+	fled->ops = &rt8515_flash_ops;
+
+	led->max_brightness = RT8515_TORCH_MAX;
+	led->brightness_set_blocking = rt8515_led_brightness_set;
+	led->flags |= LED_CORE_SUSPENDRESUME | LED_DEV_CAP_FLASH;
+
+	mutex_init(&rt->lock);
+
+	platform_set_drvdata(pdev, rt);
+
+	ret = devm_led_classdev_flash_register_ext(dev, fled, &init_data);
+	if (ret) {
+		dev_err(dev, "can't register LED %s\n", led->name);
+		mutex_destroy(&rt->lock);
+		return ret;
+	}
+
+	rt8515_init_v4l2_flash_config(rt, &v4l2_sd_cfg);
+
+	/* Create a V4L2 Flash device if V4L2 flash is enabled */
+	rt->v4l2_flash = v4l2_flash_init(dev, child, fled, NULL, &v4l2_sd_cfg);
+	if (IS_ERR(rt->v4l2_flash)) {
+		ret = PTR_ERR(rt->v4l2_flash);
+		dev_err(dev, "failed to register V4L2 flash device (%d)\n",
+			ret);
+		/*
+		 * Continue without the V4L2 flash
+		 * (we still have the classdev)
+		 */
+	}
+
+	return 0;
+}
+
+static int rt8515_remove(struct platform_device *pdev)
+{
+	struct rt8515 *rt = platform_get_drvdata(pdev);
+
+	v4l2_flash_release(rt->v4l2_flash);
+	del_timer_sync(&rt->powerdown_timer);
+	mutex_destroy(&rt->lock);
+
+	return 0;
+}
+
+static const struct of_device_id rt8515_match[] = {
+	{ .compatible = "richtek,rt8515", },
+	{ /* sentinel */ },
+};
+MODULE_DEVICE_TABLE(of, rt8515_match);
+
+static struct platform_driver rt8515_driver = {
+	.driver = {
+		.name  = "rt8515",
+		.of_match_table = rt8515_match,
+	},
+	.probe  = rt8515_probe,
+	.remove = rt8515_remove,
+};
+module_platform_driver(rt8515_driver);
+
+MODULE_AUTHOR("Linus Walleij <linus.walleij@linaro.org>");
+MODULE_DESCRIPTION("Richtek RT8515 LED driver");
+MODULE_LICENSE("GPL v2");