1*d2912cb1SThomas Gleixner // SPDX-License-Identifier: GPL-2.0-only
235c1682cSGrazvydas Ignotas /*
335c1682cSGrazvydas Ignotas * Backlight driver for Pandora handheld.
435c1682cSGrazvydas Ignotas * Pandora uses TWL4030 PWM0 -> TPS61161 combo for control backlight.
535c1682cSGrazvydas Ignotas * Based on pwm_bl.c
635c1682cSGrazvydas Ignotas *
735c1682cSGrazvydas Ignotas * Copyright 2009,2012 Gražvydas Ignotas <notasas@gmail.com>
835c1682cSGrazvydas Ignotas */
935c1682cSGrazvydas Ignotas
1035c1682cSGrazvydas Ignotas #include <linux/module.h>
1135c1682cSGrazvydas Ignotas #include <linux/kernel.h>
1235c1682cSGrazvydas Ignotas #include <linux/platform_device.h>
1335c1682cSGrazvydas Ignotas #include <linux/delay.h>
1435c1682cSGrazvydas Ignotas #include <linux/fb.h>
1535c1682cSGrazvydas Ignotas #include <linux/backlight.h>
16a2054256SWolfram Sang #include <linux/mfd/twl.h>
1735c1682cSGrazvydas Ignotas #include <linux/err.h>
1835c1682cSGrazvydas Ignotas
1935c1682cSGrazvydas Ignotas #define TWL_PWM0_ON 0x00
2035c1682cSGrazvydas Ignotas #define TWL_PWM0_OFF 0x01
2135c1682cSGrazvydas Ignotas
2235c1682cSGrazvydas Ignotas #define TWL_INTBR_GPBR1 0x0c
2335c1682cSGrazvydas Ignotas #define TWL_INTBR_PMBR1 0x0d
2435c1682cSGrazvydas Ignotas
2535c1682cSGrazvydas Ignotas #define TWL_PMBR1_PWM0_MUXMASK 0x0c
2635c1682cSGrazvydas Ignotas #define TWL_PMBR1_PWM0 0x04
2735c1682cSGrazvydas Ignotas #define PWM0_CLK_ENABLE BIT(0)
2835c1682cSGrazvydas Ignotas #define PWM0_ENABLE BIT(2)
2935c1682cSGrazvydas Ignotas
3035c1682cSGrazvydas Ignotas /* range accepted by hardware */
3135c1682cSGrazvydas Ignotas #define MIN_VALUE 9
3235c1682cSGrazvydas Ignotas #define MAX_VALUE 63
3335c1682cSGrazvydas Ignotas #define MAX_USER_VALUE (MAX_VALUE - MIN_VALUE)
3435c1682cSGrazvydas Ignotas
35832ae8e0SDaniel Vetter struct pandora_private {
36832ae8e0SDaniel Vetter unsigned old_state;
37832ae8e0SDaniel Vetter #define PANDORABL_WAS_OFF 1
38832ae8e0SDaniel Vetter };
3935c1682cSGrazvydas Ignotas
pandora_backlight_update_status(struct backlight_device * bl)4035c1682cSGrazvydas Ignotas static int pandora_backlight_update_status(struct backlight_device *bl)
4135c1682cSGrazvydas Ignotas {
4235c1682cSGrazvydas Ignotas int brightness = bl->props.brightness;
43832ae8e0SDaniel Vetter struct pandora_private *priv = bl_get_data(bl);
4435c1682cSGrazvydas Ignotas u8 r;
4535c1682cSGrazvydas Ignotas
4635c1682cSGrazvydas Ignotas if (bl->props.power != FB_BLANK_UNBLANK)
4735c1682cSGrazvydas Ignotas brightness = 0;
4835c1682cSGrazvydas Ignotas if (bl->props.state & BL_CORE_FBBLANK)
4935c1682cSGrazvydas Ignotas brightness = 0;
5035c1682cSGrazvydas Ignotas if (bl->props.state & BL_CORE_SUSPENDED)
5135c1682cSGrazvydas Ignotas brightness = 0;
5235c1682cSGrazvydas Ignotas
5335c1682cSGrazvydas Ignotas if ((unsigned int)brightness > MAX_USER_VALUE)
5435c1682cSGrazvydas Ignotas brightness = MAX_USER_VALUE;
5535c1682cSGrazvydas Ignotas
5635c1682cSGrazvydas Ignotas if (brightness == 0) {
57832ae8e0SDaniel Vetter if (priv->old_state == PANDORABL_WAS_OFF)
5835c1682cSGrazvydas Ignotas goto done;
5935c1682cSGrazvydas Ignotas
6035c1682cSGrazvydas Ignotas /* first disable PWM0 output, then clock */
6135c1682cSGrazvydas Ignotas twl_i2c_read_u8(TWL4030_MODULE_INTBR, &r, TWL_INTBR_GPBR1);
6235c1682cSGrazvydas Ignotas r &= ~PWM0_ENABLE;
6335c1682cSGrazvydas Ignotas twl_i2c_write_u8(TWL4030_MODULE_INTBR, r, TWL_INTBR_GPBR1);
6435c1682cSGrazvydas Ignotas r &= ~PWM0_CLK_ENABLE;
6535c1682cSGrazvydas Ignotas twl_i2c_write_u8(TWL4030_MODULE_INTBR, r, TWL_INTBR_GPBR1);
6635c1682cSGrazvydas Ignotas
6735c1682cSGrazvydas Ignotas goto done;
6835c1682cSGrazvydas Ignotas }
6935c1682cSGrazvydas Ignotas
70832ae8e0SDaniel Vetter if (priv->old_state == PANDORABL_WAS_OFF) {
7135c1682cSGrazvydas Ignotas /*
7235c1682cSGrazvydas Ignotas * set PWM duty cycle to max. TPS61161 seems to use this
7335c1682cSGrazvydas Ignotas * to calibrate it's PWM sensitivity when it starts.
7435c1682cSGrazvydas Ignotas */
75b444df2fSPeter Ujfalusi twl_i2c_write_u8(TWL_MODULE_PWM, MAX_VALUE, TWL_PWM0_OFF);
7635c1682cSGrazvydas Ignotas
7735c1682cSGrazvydas Ignotas /* first enable clock, then PWM0 out */
7835c1682cSGrazvydas Ignotas twl_i2c_read_u8(TWL4030_MODULE_INTBR, &r, TWL_INTBR_GPBR1);
7935c1682cSGrazvydas Ignotas r &= ~PWM0_ENABLE;
8035c1682cSGrazvydas Ignotas r |= PWM0_CLK_ENABLE;
8135c1682cSGrazvydas Ignotas twl_i2c_write_u8(TWL4030_MODULE_INTBR, r, TWL_INTBR_GPBR1);
8235c1682cSGrazvydas Ignotas r |= PWM0_ENABLE;
8335c1682cSGrazvydas Ignotas twl_i2c_write_u8(TWL4030_MODULE_INTBR, r, TWL_INTBR_GPBR1);
8435c1682cSGrazvydas Ignotas
8535c1682cSGrazvydas Ignotas /*
8635c1682cSGrazvydas Ignotas * TI made it very easy to enable digital control, so easy that
8735c1682cSGrazvydas Ignotas * it often triggers unintentionally and disabes PWM control,
8835c1682cSGrazvydas Ignotas * so wait until 1 wire mode detection window ends.
8935c1682cSGrazvydas Ignotas */
9035c1682cSGrazvydas Ignotas usleep_range(2000, 10000);
9135c1682cSGrazvydas Ignotas }
9235c1682cSGrazvydas Ignotas
93b444df2fSPeter Ujfalusi twl_i2c_write_u8(TWL_MODULE_PWM, MIN_VALUE + brightness, TWL_PWM0_OFF);
9435c1682cSGrazvydas Ignotas
9535c1682cSGrazvydas Ignotas done:
9635c1682cSGrazvydas Ignotas if (brightness != 0)
97832ae8e0SDaniel Vetter priv->old_state = 0;
9835c1682cSGrazvydas Ignotas else
99832ae8e0SDaniel Vetter priv->old_state = PANDORABL_WAS_OFF;
10035c1682cSGrazvydas Ignotas
10135c1682cSGrazvydas Ignotas return 0;
10235c1682cSGrazvydas Ignotas }
10335c1682cSGrazvydas Ignotas
10435c1682cSGrazvydas Ignotas static const struct backlight_ops pandora_backlight_ops = {
10535c1682cSGrazvydas Ignotas .options = BL_CORE_SUSPENDRESUME,
10635c1682cSGrazvydas Ignotas .update_status = pandora_backlight_update_status,
10735c1682cSGrazvydas Ignotas };
10835c1682cSGrazvydas Ignotas
pandora_backlight_probe(struct platform_device * pdev)10935c1682cSGrazvydas Ignotas static int pandora_backlight_probe(struct platform_device *pdev)
11035c1682cSGrazvydas Ignotas {
11135c1682cSGrazvydas Ignotas struct backlight_properties props;
11235c1682cSGrazvydas Ignotas struct backlight_device *bl;
113832ae8e0SDaniel Vetter struct pandora_private *priv;
11435c1682cSGrazvydas Ignotas u8 r;
11535c1682cSGrazvydas Ignotas
116832ae8e0SDaniel Vetter priv = devm_kmalloc(&pdev->dev, sizeof(*priv), GFP_KERNEL);
117832ae8e0SDaniel Vetter if (!priv) {
118832ae8e0SDaniel Vetter dev_err(&pdev->dev, "failed to allocate driver private data\n");
119832ae8e0SDaniel Vetter return -ENOMEM;
120832ae8e0SDaniel Vetter }
121832ae8e0SDaniel Vetter
12235c1682cSGrazvydas Ignotas memset(&props, 0, sizeof(props));
12335c1682cSGrazvydas Ignotas props.max_brightness = MAX_USER_VALUE;
12435c1682cSGrazvydas Ignotas props.type = BACKLIGHT_RAW;
12575d4baecSJingoo Han bl = devm_backlight_device_register(&pdev->dev, pdev->name, &pdev->dev,
126832ae8e0SDaniel Vetter priv, &pandora_backlight_ops, &props);
12735c1682cSGrazvydas Ignotas if (IS_ERR(bl)) {
12835c1682cSGrazvydas Ignotas dev_err(&pdev->dev, "failed to register backlight\n");
12935c1682cSGrazvydas Ignotas return PTR_ERR(bl);
13035c1682cSGrazvydas Ignotas }
13135c1682cSGrazvydas Ignotas
13235c1682cSGrazvydas Ignotas platform_set_drvdata(pdev, bl);
13335c1682cSGrazvydas Ignotas
13435c1682cSGrazvydas Ignotas /* 64 cycle period, ON position 0 */
135b444df2fSPeter Ujfalusi twl_i2c_write_u8(TWL_MODULE_PWM, 0x80, TWL_PWM0_ON);
13635c1682cSGrazvydas Ignotas
137832ae8e0SDaniel Vetter priv->old_state = PANDORABL_WAS_OFF;
13835c1682cSGrazvydas Ignotas bl->props.brightness = MAX_USER_VALUE;
13935c1682cSGrazvydas Ignotas backlight_update_status(bl);
14035c1682cSGrazvydas Ignotas
14135c1682cSGrazvydas Ignotas /* enable PWM function in pin mux */
14235c1682cSGrazvydas Ignotas twl_i2c_read_u8(TWL4030_MODULE_INTBR, &r, TWL_INTBR_PMBR1);
14335c1682cSGrazvydas Ignotas r &= ~TWL_PMBR1_PWM0_MUXMASK;
14435c1682cSGrazvydas Ignotas r |= TWL_PMBR1_PWM0;
14535c1682cSGrazvydas Ignotas twl_i2c_write_u8(TWL4030_MODULE_INTBR, r, TWL_INTBR_PMBR1);
14635c1682cSGrazvydas Ignotas
14735c1682cSGrazvydas Ignotas return 0;
14835c1682cSGrazvydas Ignotas }
14935c1682cSGrazvydas Ignotas
15035c1682cSGrazvydas Ignotas static struct platform_driver pandora_backlight_driver = {
15135c1682cSGrazvydas Ignotas .driver = {
15235c1682cSGrazvydas Ignotas .name = "pandora-backlight",
15335c1682cSGrazvydas Ignotas },
15435c1682cSGrazvydas Ignotas .probe = pandora_backlight_probe,
15535c1682cSGrazvydas Ignotas };
15635c1682cSGrazvydas Ignotas
15735c1682cSGrazvydas Ignotas module_platform_driver(pandora_backlight_driver);
15835c1682cSGrazvydas Ignotas
15935c1682cSGrazvydas Ignotas MODULE_AUTHOR("Gražvydas Ignotas <notasas@gmail.com>");
16035c1682cSGrazvydas Ignotas MODULE_DESCRIPTION("Pandora Backlight Driver");
16135c1682cSGrazvydas Ignotas MODULE_LICENSE("GPL");
16235c1682cSGrazvydas Ignotas MODULE_ALIAS("platform:pandora-backlight");
163