18c2ecf20Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-only
28c2ecf20Sopenharmony_ci/*
38c2ecf20Sopenharmony_ci * Backlight driver for Pandora handheld.
48c2ecf20Sopenharmony_ci * Pandora uses TWL4030 PWM0 -> TPS61161 combo for control backlight.
58c2ecf20Sopenharmony_ci * Based on pwm_bl.c
68c2ecf20Sopenharmony_ci *
78c2ecf20Sopenharmony_ci * Copyright 2009,2012 Gražvydas Ignotas <notasas@gmail.com>
88c2ecf20Sopenharmony_ci */
98c2ecf20Sopenharmony_ci
108c2ecf20Sopenharmony_ci#include <linux/module.h>
118c2ecf20Sopenharmony_ci#include <linux/kernel.h>
128c2ecf20Sopenharmony_ci#include <linux/platform_device.h>
138c2ecf20Sopenharmony_ci#include <linux/delay.h>
148c2ecf20Sopenharmony_ci#include <linux/fb.h>
158c2ecf20Sopenharmony_ci#include <linux/backlight.h>
168c2ecf20Sopenharmony_ci#include <linux/mfd/twl.h>
178c2ecf20Sopenharmony_ci#include <linux/err.h>
188c2ecf20Sopenharmony_ci
198c2ecf20Sopenharmony_ci#define TWL_PWM0_ON		0x00
208c2ecf20Sopenharmony_ci#define TWL_PWM0_OFF		0x01
218c2ecf20Sopenharmony_ci
228c2ecf20Sopenharmony_ci#define TWL_INTBR_GPBR1		0x0c
238c2ecf20Sopenharmony_ci#define TWL_INTBR_PMBR1		0x0d
248c2ecf20Sopenharmony_ci
258c2ecf20Sopenharmony_ci#define TWL_PMBR1_PWM0_MUXMASK	0x0c
268c2ecf20Sopenharmony_ci#define TWL_PMBR1_PWM0		0x04
278c2ecf20Sopenharmony_ci#define PWM0_CLK_ENABLE		BIT(0)
288c2ecf20Sopenharmony_ci#define PWM0_ENABLE		BIT(2)
298c2ecf20Sopenharmony_ci
308c2ecf20Sopenharmony_ci/* range accepted by hardware */
318c2ecf20Sopenharmony_ci#define MIN_VALUE 9
328c2ecf20Sopenharmony_ci#define MAX_VALUE 63
338c2ecf20Sopenharmony_ci#define MAX_USER_VALUE (MAX_VALUE - MIN_VALUE)
348c2ecf20Sopenharmony_ci
358c2ecf20Sopenharmony_cistruct pandora_private {
368c2ecf20Sopenharmony_ci	unsigned old_state;
378c2ecf20Sopenharmony_ci#define PANDORABL_WAS_OFF 1
388c2ecf20Sopenharmony_ci};
398c2ecf20Sopenharmony_ci
408c2ecf20Sopenharmony_cistatic int pandora_backlight_update_status(struct backlight_device *bl)
418c2ecf20Sopenharmony_ci{
428c2ecf20Sopenharmony_ci	int brightness = bl->props.brightness;
438c2ecf20Sopenharmony_ci	struct pandora_private *priv = bl_get_data(bl);
448c2ecf20Sopenharmony_ci	u8 r;
458c2ecf20Sopenharmony_ci
468c2ecf20Sopenharmony_ci	if (bl->props.power != FB_BLANK_UNBLANK)
478c2ecf20Sopenharmony_ci		brightness = 0;
488c2ecf20Sopenharmony_ci	if (bl->props.state & BL_CORE_FBBLANK)
498c2ecf20Sopenharmony_ci		brightness = 0;
508c2ecf20Sopenharmony_ci	if (bl->props.state & BL_CORE_SUSPENDED)
518c2ecf20Sopenharmony_ci		brightness = 0;
528c2ecf20Sopenharmony_ci
538c2ecf20Sopenharmony_ci	if ((unsigned int)brightness > MAX_USER_VALUE)
548c2ecf20Sopenharmony_ci		brightness = MAX_USER_VALUE;
558c2ecf20Sopenharmony_ci
568c2ecf20Sopenharmony_ci	if (brightness == 0) {
578c2ecf20Sopenharmony_ci		if (priv->old_state == PANDORABL_WAS_OFF)
588c2ecf20Sopenharmony_ci			goto done;
598c2ecf20Sopenharmony_ci
608c2ecf20Sopenharmony_ci		/* first disable PWM0 output, then clock */
618c2ecf20Sopenharmony_ci		twl_i2c_read_u8(TWL4030_MODULE_INTBR, &r, TWL_INTBR_GPBR1);
628c2ecf20Sopenharmony_ci		r &= ~PWM0_ENABLE;
638c2ecf20Sopenharmony_ci		twl_i2c_write_u8(TWL4030_MODULE_INTBR, r, TWL_INTBR_GPBR1);
648c2ecf20Sopenharmony_ci		r &= ~PWM0_CLK_ENABLE;
658c2ecf20Sopenharmony_ci		twl_i2c_write_u8(TWL4030_MODULE_INTBR, r, TWL_INTBR_GPBR1);
668c2ecf20Sopenharmony_ci
678c2ecf20Sopenharmony_ci		goto done;
688c2ecf20Sopenharmony_ci	}
698c2ecf20Sopenharmony_ci
708c2ecf20Sopenharmony_ci	if (priv->old_state == PANDORABL_WAS_OFF) {
718c2ecf20Sopenharmony_ci		/*
728c2ecf20Sopenharmony_ci		 * set PWM duty cycle to max. TPS61161 seems to use this
738c2ecf20Sopenharmony_ci		 * to calibrate it's PWM sensitivity when it starts.
748c2ecf20Sopenharmony_ci		 */
758c2ecf20Sopenharmony_ci		twl_i2c_write_u8(TWL_MODULE_PWM, MAX_VALUE, TWL_PWM0_OFF);
768c2ecf20Sopenharmony_ci
778c2ecf20Sopenharmony_ci		/* first enable clock, then PWM0 out */
788c2ecf20Sopenharmony_ci		twl_i2c_read_u8(TWL4030_MODULE_INTBR, &r, TWL_INTBR_GPBR1);
798c2ecf20Sopenharmony_ci		r &= ~PWM0_ENABLE;
808c2ecf20Sopenharmony_ci		r |= PWM0_CLK_ENABLE;
818c2ecf20Sopenharmony_ci		twl_i2c_write_u8(TWL4030_MODULE_INTBR, r, TWL_INTBR_GPBR1);
828c2ecf20Sopenharmony_ci		r |= PWM0_ENABLE;
838c2ecf20Sopenharmony_ci		twl_i2c_write_u8(TWL4030_MODULE_INTBR, r, TWL_INTBR_GPBR1);
848c2ecf20Sopenharmony_ci
858c2ecf20Sopenharmony_ci		/*
868c2ecf20Sopenharmony_ci		 * TI made it very easy to enable digital control, so easy that
878c2ecf20Sopenharmony_ci		 * it often triggers unintentionally and disabes PWM control,
888c2ecf20Sopenharmony_ci		 * so wait until 1 wire mode detection window ends.
898c2ecf20Sopenharmony_ci		 */
908c2ecf20Sopenharmony_ci		usleep_range(2000, 10000);
918c2ecf20Sopenharmony_ci	}
928c2ecf20Sopenharmony_ci
938c2ecf20Sopenharmony_ci	twl_i2c_write_u8(TWL_MODULE_PWM, MIN_VALUE + brightness, TWL_PWM0_OFF);
948c2ecf20Sopenharmony_ci
958c2ecf20Sopenharmony_cidone:
968c2ecf20Sopenharmony_ci	if (brightness != 0)
978c2ecf20Sopenharmony_ci		priv->old_state = 0;
988c2ecf20Sopenharmony_ci	else
998c2ecf20Sopenharmony_ci		priv->old_state = PANDORABL_WAS_OFF;
1008c2ecf20Sopenharmony_ci
1018c2ecf20Sopenharmony_ci	return 0;
1028c2ecf20Sopenharmony_ci}
1038c2ecf20Sopenharmony_ci
1048c2ecf20Sopenharmony_cistatic const struct backlight_ops pandora_backlight_ops = {
1058c2ecf20Sopenharmony_ci	.options	= BL_CORE_SUSPENDRESUME,
1068c2ecf20Sopenharmony_ci	.update_status	= pandora_backlight_update_status,
1078c2ecf20Sopenharmony_ci};
1088c2ecf20Sopenharmony_ci
1098c2ecf20Sopenharmony_cistatic int pandora_backlight_probe(struct platform_device *pdev)
1108c2ecf20Sopenharmony_ci{
1118c2ecf20Sopenharmony_ci	struct backlight_properties props;
1128c2ecf20Sopenharmony_ci	struct backlight_device *bl;
1138c2ecf20Sopenharmony_ci	struct pandora_private *priv;
1148c2ecf20Sopenharmony_ci	u8 r;
1158c2ecf20Sopenharmony_ci
1168c2ecf20Sopenharmony_ci	priv = devm_kmalloc(&pdev->dev, sizeof(*priv), GFP_KERNEL);
1178c2ecf20Sopenharmony_ci	if (!priv) {
1188c2ecf20Sopenharmony_ci		dev_err(&pdev->dev, "failed to allocate driver private data\n");
1198c2ecf20Sopenharmony_ci		return -ENOMEM;
1208c2ecf20Sopenharmony_ci	}
1218c2ecf20Sopenharmony_ci
1228c2ecf20Sopenharmony_ci	memset(&props, 0, sizeof(props));
1238c2ecf20Sopenharmony_ci	props.max_brightness = MAX_USER_VALUE;
1248c2ecf20Sopenharmony_ci	props.type = BACKLIGHT_RAW;
1258c2ecf20Sopenharmony_ci	bl = devm_backlight_device_register(&pdev->dev, pdev->name, &pdev->dev,
1268c2ecf20Sopenharmony_ci					priv, &pandora_backlight_ops, &props);
1278c2ecf20Sopenharmony_ci	if (IS_ERR(bl)) {
1288c2ecf20Sopenharmony_ci		dev_err(&pdev->dev, "failed to register backlight\n");
1298c2ecf20Sopenharmony_ci		return PTR_ERR(bl);
1308c2ecf20Sopenharmony_ci	}
1318c2ecf20Sopenharmony_ci
1328c2ecf20Sopenharmony_ci	platform_set_drvdata(pdev, bl);
1338c2ecf20Sopenharmony_ci
1348c2ecf20Sopenharmony_ci	/* 64 cycle period, ON position 0 */
1358c2ecf20Sopenharmony_ci	twl_i2c_write_u8(TWL_MODULE_PWM, 0x80, TWL_PWM0_ON);
1368c2ecf20Sopenharmony_ci
1378c2ecf20Sopenharmony_ci	priv->old_state = PANDORABL_WAS_OFF;
1388c2ecf20Sopenharmony_ci	bl->props.brightness = MAX_USER_VALUE;
1398c2ecf20Sopenharmony_ci	backlight_update_status(bl);
1408c2ecf20Sopenharmony_ci
1418c2ecf20Sopenharmony_ci	/* enable PWM function in pin mux */
1428c2ecf20Sopenharmony_ci	twl_i2c_read_u8(TWL4030_MODULE_INTBR, &r, TWL_INTBR_PMBR1);
1438c2ecf20Sopenharmony_ci	r &= ~TWL_PMBR1_PWM0_MUXMASK;
1448c2ecf20Sopenharmony_ci	r |= TWL_PMBR1_PWM0;
1458c2ecf20Sopenharmony_ci	twl_i2c_write_u8(TWL4030_MODULE_INTBR, r, TWL_INTBR_PMBR1);
1468c2ecf20Sopenharmony_ci
1478c2ecf20Sopenharmony_ci	return 0;
1488c2ecf20Sopenharmony_ci}
1498c2ecf20Sopenharmony_ci
1508c2ecf20Sopenharmony_cistatic struct platform_driver pandora_backlight_driver = {
1518c2ecf20Sopenharmony_ci	.driver		= {
1528c2ecf20Sopenharmony_ci		.name	= "pandora-backlight",
1538c2ecf20Sopenharmony_ci	},
1548c2ecf20Sopenharmony_ci	.probe		= pandora_backlight_probe,
1558c2ecf20Sopenharmony_ci};
1568c2ecf20Sopenharmony_ci
1578c2ecf20Sopenharmony_cimodule_platform_driver(pandora_backlight_driver);
1588c2ecf20Sopenharmony_ci
1598c2ecf20Sopenharmony_ciMODULE_AUTHOR("Gražvydas Ignotas <notasas@gmail.com>");
1608c2ecf20Sopenharmony_ciMODULE_DESCRIPTION("Pandora Backlight Driver");
1618c2ecf20Sopenharmony_ciMODULE_LICENSE("GPL");
1628c2ecf20Sopenharmony_ciMODULE_ALIAS("platform:pandora-backlight");
163