xref: /openbmc/linux/drivers/video/backlight/pandora_bl.c (revision 75bf465f0bc33e9b776a46d6a1b9b990f5fb7c37)
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