Loading...
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 | // SPDX-License-Identifier: GPL-2.0-only /* * Backlight driver for the Kinetic KTD253 * Based on code and know-how from the Samsung GT-S7710 * Gareth Phillips <gareth.phillips@samsung.com> */ #include <linux/backlight.h> #include <linux/delay.h> #include <linux/err.h> #include <linux/fb.h> #include <linux/gpio/consumer.h> #include <linux/init.h> #include <linux/kernel.h> #include <linux/limits.h> #include <linux/module.h> #include <linux/of.h> #include <linux/platform_device.h> #include <linux/property.h> #include <linux/slab.h> /* Current ratio is n/32 from 1/32 to 32/32 */ #define KTD253_MIN_RATIO 1 #define KTD253_MAX_RATIO 32 #define KTD253_DEFAULT_RATIO 13 #define KTD253_T_LOW_NS (200 + 10) /* Additional 10ns as safety factor */ #define KTD253_T_HIGH_NS (200 + 10) /* Additional 10ns as safety factor */ #define KTD253_T_OFF_CRIT_NS 100000 /* 100 us, now it doesn't look good */ #define KTD253_T_OFF_MS 3 struct ktd253_backlight { struct device *dev; struct backlight_device *bl; struct gpio_desc *gpiod; u16 ratio; }; static void ktd253_backlight_set_max_ratio(struct ktd253_backlight *ktd253) { gpiod_set_value_cansleep(ktd253->gpiod, 1); ndelay(KTD253_T_HIGH_NS); /* We always fall back to this when we power on */ } static int ktd253_backlight_stepdown(struct ktd253_backlight *ktd253) { /* * These GPIO operations absolutely can NOT sleep so no _cansleep * suffixes, and no using GPIO expanders on slow buses for this! * * The maximum number of cycles of the loop is 32 so the time taken * should nominally be: * (T_LOW_NS + T_HIGH_NS + loop_time) * 32 * * Architectures do not always support ndelay() and we will get a few us * instead. If we get to a critical time limit an interrupt has likely * occured in the low part of the loop and we need to restart from the * top so we have the backlight in a known state. */ u64 ns; ns = ktime_get_ns(); gpiod_set_value(ktd253->gpiod, 0); ndelay(KTD253_T_LOW_NS); gpiod_set_value(ktd253->gpiod, 1); ns = ktime_get_ns() - ns; if (ns >= KTD253_T_OFF_CRIT_NS) { dev_err(ktd253->dev, "PCM on backlight took too long (%llu ns)\n", ns); return -EAGAIN; } ndelay(KTD253_T_HIGH_NS); return 0; } static int ktd253_backlight_update_status(struct backlight_device *bl) { struct ktd253_backlight *ktd253 = bl_get_data(bl); int brightness = backlight_get_brightness(bl); u16 target_ratio; u16 current_ratio = ktd253->ratio; int ret; dev_dbg(ktd253->dev, "new brightness/ratio: %d/32\n", brightness); target_ratio = brightness; if (target_ratio == current_ratio) /* This is already right */ return 0; if (target_ratio == 0) { gpiod_set_value_cansleep(ktd253->gpiod, 0); /* * We need to keep the GPIO low for at least this long * to actually switch the KTD253 off. */ msleep(KTD253_T_OFF_MS); ktd253->ratio = 0; return 0; } if (current_ratio == 0) { ktd253_backlight_set_max_ratio(ktd253); current_ratio = KTD253_MAX_RATIO; } while (current_ratio != target_ratio) { /* * These GPIO operations absolutely can NOT sleep so no * _cansleep suffixes, and no using GPIO expanders on * slow buses for this! */ ret = ktd253_backlight_stepdown(ktd253); if (ret == -EAGAIN) { /* * Something disturbed the backlight setting code when * running so we need to bring the PWM back to a known * state. This shouldn't happen too much. */ gpiod_set_value_cansleep(ktd253->gpiod, 0); msleep(KTD253_T_OFF_MS); ktd253_backlight_set_max_ratio(ktd253); current_ratio = KTD253_MAX_RATIO; } else if (current_ratio == KTD253_MIN_RATIO) { /* After 1/32 we loop back to 32/32 */ current_ratio = KTD253_MAX_RATIO; } else { current_ratio--; } } ktd253->ratio = current_ratio; dev_dbg(ktd253->dev, "new ratio set to %d/32\n", target_ratio); return 0; } static const struct backlight_ops ktd253_backlight_ops = { .options = BL_CORE_SUSPENDRESUME, .update_status = ktd253_backlight_update_status, }; static int ktd253_backlight_probe(struct platform_device *pdev) { struct device *dev = &pdev->dev; struct backlight_device *bl; struct ktd253_backlight *ktd253; u32 max_brightness; u32 brightness; int ret; ktd253 = devm_kzalloc(dev, sizeof(*ktd253), GFP_KERNEL); if (!ktd253) return -ENOMEM; ktd253->dev = dev; ret = device_property_read_u32(dev, "max-brightness", &max_brightness); if (ret) max_brightness = KTD253_MAX_RATIO; if (max_brightness > KTD253_MAX_RATIO) { /* Clamp brightness to hardware max */ dev_err(dev, "illegal max brightness specified\n"); max_brightness = KTD253_MAX_RATIO; } ret = device_property_read_u32(dev, "default-brightness", &brightness); if (ret) brightness = KTD253_DEFAULT_RATIO; if (brightness > max_brightness) { /* Clamp default brightness to max brightness */ dev_err(dev, "default brightness exceeds max brightness\n"); brightness = max_brightness; } ktd253->gpiod = devm_gpiod_get(dev, "enable", GPIOD_OUT_LOW); if (IS_ERR(ktd253->gpiod)) return dev_err_probe(dev, PTR_ERR(ktd253->gpiod), "gpio line missing or invalid.\n"); gpiod_set_consumer_name(ktd253->gpiod, dev_name(dev)); /* Bring backlight to a known off state */ msleep(KTD253_T_OFF_MS); bl = devm_backlight_device_register(dev, dev_name(dev), dev, ktd253, &ktd253_backlight_ops, NULL); if (IS_ERR(bl)) { dev_err(dev, "failed to register backlight\n"); return PTR_ERR(bl); } bl->props.max_brightness = max_brightness; /* When we just enable the GPIO line we set max brightness */ if (brightness) { bl->props.brightness = brightness; bl->props.power = FB_BLANK_UNBLANK; } else { bl->props.brightness = 0; bl->props.power = FB_BLANK_POWERDOWN; } ktd253->bl = bl; platform_set_drvdata(pdev, bl); backlight_update_status(bl); return 0; } static const struct of_device_id ktd253_backlight_of_match[] = { { .compatible = "kinetic,ktd253" }, { .compatible = "kinetic,ktd259" }, { /* sentinel */ } }; MODULE_DEVICE_TABLE(of, ktd253_backlight_of_match); static struct platform_driver ktd253_backlight_driver = { .driver = { .name = "ktd253-backlight", .of_match_table = ktd253_backlight_of_match, }, .probe = ktd253_backlight_probe, }; module_platform_driver(ktd253_backlight_driver); MODULE_AUTHOR("Linus Walleij <linus.walleij@linaro.org>"); MODULE_DESCRIPTION("Kinetic KTD253 Backlight Driver"); MODULE_LICENSE("GPL"); MODULE_ALIAS("platform:ktd253-backlight"); |