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