1 // SPDX-License-Identifier: GPL-2.0-only 2 /* 3 * Backlight driver for Pandora handheld. 4 * Pandora uses TWL4030 PWM0 -> TPS61161 combo for control backlight. 5 * Based on pwm_bl.c 6 * 7 * Copyright 2009,2012 Gražvydas Ignotas <notasas@gmail.com> 8 */ 9 10 #include <linux/module.h> 11 #include <linux/kernel.h> 12 #include <linux/platform_device.h> 13 #include <linux/delay.h> 14 #include <linux/fb.h> 15 #include <linux/backlight.h> 16 #include <linux/mfd/twl.h> 17 #include <linux/err.h> 18 19 #define TWL_PWM0_ON 0x00 20 #define TWL_PWM0_OFF 0x01 21 22 #define TWL_INTBR_GPBR1 0x0c 23 #define TWL_INTBR_PMBR1 0x0d 24 25 #define TWL_PMBR1_PWM0_MUXMASK 0x0c 26 #define TWL_PMBR1_PWM0 0x04 27 #define PWM0_CLK_ENABLE BIT(0) 28 #define PWM0_ENABLE BIT(2) 29 30 /* range accepted by hardware */ 31 #define MIN_VALUE 9 32 #define MAX_VALUE 63 33 #define MAX_USER_VALUE (MAX_VALUE - MIN_VALUE) 34 35 struct pandora_private { 36 unsigned old_state; 37 #define PANDORABL_WAS_OFF 1 38 }; 39 40 static int pandora_backlight_update_status(struct backlight_device *bl) 41 { 42 int brightness = bl->props.brightness; 43 struct pandora_private *priv = bl_get_data(bl); 44 u8 r; 45 46 if (bl->props.power != FB_BLANK_UNBLANK) 47 brightness = 0; 48 if (bl->props.state & BL_CORE_FBBLANK) 49 brightness = 0; 50 if (bl->props.state & BL_CORE_SUSPENDED) 51 brightness = 0; 52 53 if ((unsigned int)brightness > MAX_USER_VALUE) 54 brightness = MAX_USER_VALUE; 55 56 if (brightness == 0) { 57 if (priv->old_state == PANDORABL_WAS_OFF) 58 goto done; 59 60 /* first disable PWM0 output, then clock */ 61 twl_i2c_read_u8(TWL4030_MODULE_INTBR, &r, TWL_INTBR_GPBR1); 62 r &= ~PWM0_ENABLE; 63 twl_i2c_write_u8(TWL4030_MODULE_INTBR, r, TWL_INTBR_GPBR1); 64 r &= ~PWM0_CLK_ENABLE; 65 twl_i2c_write_u8(TWL4030_MODULE_INTBR, r, TWL_INTBR_GPBR1); 66 67 goto done; 68 } 69 70 if (priv->old_state == PANDORABL_WAS_OFF) { 71 /* 72 * set PWM duty cycle to max. TPS61161 seems to use this 73 * to calibrate it's PWM sensitivity when it starts. 74 */ 75 twl_i2c_write_u8(TWL_MODULE_PWM, MAX_VALUE, TWL_PWM0_OFF); 76 77 /* first enable clock, then PWM0 out */ 78 twl_i2c_read_u8(TWL4030_MODULE_INTBR, &r, TWL_INTBR_GPBR1); 79 r &= ~PWM0_ENABLE; 80 r |= PWM0_CLK_ENABLE; 81 twl_i2c_write_u8(TWL4030_MODULE_INTBR, r, TWL_INTBR_GPBR1); 82 r |= PWM0_ENABLE; 83 twl_i2c_write_u8(TWL4030_MODULE_INTBR, r, TWL_INTBR_GPBR1); 84 85 /* 86 * TI made it very easy to enable digital control, so easy that 87 * it often triggers unintentionally and disabes PWM control, 88 * so wait until 1 wire mode detection window ends. 89 */ 90 usleep_range(2000, 10000); 91 } 92 93 twl_i2c_write_u8(TWL_MODULE_PWM, MIN_VALUE + brightness, TWL_PWM0_OFF); 94 95 done: 96 if (brightness != 0) 97 priv->old_state = 0; 98 else 99 priv->old_state = PANDORABL_WAS_OFF; 100 101 return 0; 102 } 103 104 static const struct backlight_ops pandora_backlight_ops = { 105 .options = BL_CORE_SUSPENDRESUME, 106 .update_status = pandora_backlight_update_status, 107 }; 108 109 static int pandora_backlight_probe(struct platform_device *pdev) 110 { 111 struct backlight_properties props; 112 struct backlight_device *bl; 113 struct pandora_private *priv; 114 u8 r; 115 116 priv = devm_kmalloc(&pdev->dev, sizeof(*priv), GFP_KERNEL); 117 if (!priv) { 118 dev_err(&pdev->dev, "failed to allocate driver private data\n"); 119 return -ENOMEM; 120 } 121 122 memset(&props, 0, sizeof(props)); 123 props.max_brightness = MAX_USER_VALUE; 124 props.type = BACKLIGHT_RAW; 125 bl = devm_backlight_device_register(&pdev->dev, pdev->name, &pdev->dev, 126 priv, &pandora_backlight_ops, &props); 127 if (IS_ERR(bl)) { 128 dev_err(&pdev->dev, "failed to register backlight\n"); 129 return PTR_ERR(bl); 130 } 131 132 platform_set_drvdata(pdev, bl); 133 134 /* 64 cycle period, ON position 0 */ 135 twl_i2c_write_u8(TWL_MODULE_PWM, 0x80, TWL_PWM0_ON); 136 137 priv->old_state = PANDORABL_WAS_OFF; 138 bl->props.brightness = MAX_USER_VALUE; 139 backlight_update_status(bl); 140 141 /* enable PWM function in pin mux */ 142 twl_i2c_read_u8(TWL4030_MODULE_INTBR, &r, TWL_INTBR_PMBR1); 143 r &= ~TWL_PMBR1_PWM0_MUXMASK; 144 r |= TWL_PMBR1_PWM0; 145 twl_i2c_write_u8(TWL4030_MODULE_INTBR, r, TWL_INTBR_PMBR1); 146 147 return 0; 148 } 149 150 static struct platform_driver pandora_backlight_driver = { 151 .driver = { 152 .name = "pandora-backlight", 153 }, 154 .probe = pandora_backlight_probe, 155 }; 156 157 module_platform_driver(pandora_backlight_driver); 158 159 MODULE_AUTHOR("Gražvydas Ignotas <notasas@gmail.com>"); 160 MODULE_DESCRIPTION("Pandora Backlight Driver"); 161 MODULE_LICENSE("GPL"); 162 MODULE_ALIAS("platform:pandora-backlight"); 163