1 | // SPDX-License-Identifier: GPL-2.0+ |
2 | |
3 | #include <linux/leds.h> |
4 | #include <linux/module.h> |
5 | #include <linux/platform_device.h> |
6 | #include <linux/regmap.h> |
7 | |
8 | #define A500_EC_LED_DELAY_USEC (100 * 1000) |
9 | |
10 | enum { |
11 | REG_RESET_LEDS = 0x40, |
12 | REG_POWER_LED_ON = 0x42, |
13 | REG_CHARGE_LED_ON = 0x43, |
14 | REG_ANDROID_LEDS_OFF = 0x5a, |
15 | }; |
16 | |
17 | struct a500_led { |
18 | struct led_classdev cdev; |
19 | const struct reg_sequence *enable_seq; |
20 | struct a500_led *other; |
21 | struct regmap *rmap; |
22 | }; |
23 | |
24 | static const struct reg_sequence a500_ec_leds_reset_seq[] = { |
25 | REG_SEQ(REG_RESET_LEDS, 0x0, A500_EC_LED_DELAY_USEC), |
26 | REG_SEQ(REG_ANDROID_LEDS_OFF, 0x0, A500_EC_LED_DELAY_USEC), |
27 | }; |
28 | |
29 | static const struct reg_sequence a500_ec_white_led_enable_seq[] = { |
30 | REG_SEQ(REG_POWER_LED_ON, 0x0, A500_EC_LED_DELAY_USEC), |
31 | }; |
32 | |
33 | static const struct reg_sequence a500_ec_orange_led_enable_seq[] = { |
34 | REG_SEQ(REG_CHARGE_LED_ON, 0x0, A500_EC_LED_DELAY_USEC), |
35 | }; |
36 | |
37 | static int a500_ec_led_brightness_set(struct led_classdev *led_cdev, |
38 | enum led_brightness value) |
39 | { |
40 | struct a500_led *led = container_of(led_cdev, struct a500_led, cdev); |
41 | struct reg_sequence control_seq[2]; |
42 | unsigned int num_regs = 1; |
43 | |
44 | if (value) { |
45 | control_seq[0] = led->enable_seq[0]; |
46 | } else { |
47 | /* |
48 | * There is no separate controls which can disable LEDs |
49 | * individually, there is only RESET_LEDS command that turns |
50 | * off both LEDs. |
51 | * |
52 | * RESET_LEDS turns off both LEDs, thus restore other LED if |
53 | * it's turned ON. |
54 | */ |
55 | if (led->other->cdev.brightness) |
56 | num_regs = 2; |
57 | |
58 | control_seq[0] = a500_ec_leds_reset_seq[0]; |
59 | control_seq[1] = led->other->enable_seq[0]; |
60 | } |
61 | |
62 | return regmap_multi_reg_write(map: led->rmap, regs: control_seq, num_regs); |
63 | } |
64 | |
65 | static int a500_ec_leds_probe(struct platform_device *pdev) |
66 | { |
67 | struct a500_led *white_led, *orange_led; |
68 | struct regmap *rmap; |
69 | int err; |
70 | |
71 | rmap = dev_get_regmap(dev: pdev->dev.parent, name: "KB930" ); |
72 | if (!rmap) |
73 | return -EINVAL; |
74 | |
75 | /* reset and turn off LEDs */ |
76 | regmap_multi_reg_write(map: rmap, regs: a500_ec_leds_reset_seq, num_regs: 2); |
77 | |
78 | white_led = devm_kzalloc(dev: &pdev->dev, size: sizeof(*white_led), GFP_KERNEL); |
79 | if (!white_led) |
80 | return -ENOMEM; |
81 | |
82 | white_led->cdev.name = "power:white" ; |
83 | white_led->cdev.brightness_set_blocking = a500_ec_led_brightness_set; |
84 | white_led->cdev.flags = LED_CORE_SUSPENDRESUME; |
85 | white_led->cdev.max_brightness = 1; |
86 | white_led->enable_seq = a500_ec_white_led_enable_seq; |
87 | white_led->rmap = rmap; |
88 | |
89 | orange_led = devm_kzalloc(dev: &pdev->dev, size: sizeof(*orange_led), GFP_KERNEL); |
90 | if (!orange_led) |
91 | return -ENOMEM; |
92 | |
93 | orange_led->cdev.name = "power:orange" ; |
94 | orange_led->cdev.brightness_set_blocking = a500_ec_led_brightness_set; |
95 | orange_led->cdev.flags = LED_CORE_SUSPENDRESUME; |
96 | orange_led->cdev.max_brightness = 1; |
97 | orange_led->enable_seq = a500_ec_orange_led_enable_seq; |
98 | orange_led->rmap = rmap; |
99 | |
100 | white_led->other = orange_led; |
101 | orange_led->other = white_led; |
102 | |
103 | err = devm_led_classdev_register(parent: &pdev->dev, led_cdev: &white_led->cdev); |
104 | if (err) { |
105 | dev_err(&pdev->dev, "failed to register white LED\n" ); |
106 | return err; |
107 | } |
108 | |
109 | err = devm_led_classdev_register(parent: &pdev->dev, led_cdev: &orange_led->cdev); |
110 | if (err) { |
111 | dev_err(&pdev->dev, "failed to register orange LED\n" ); |
112 | return err; |
113 | } |
114 | |
115 | return 0; |
116 | } |
117 | |
118 | static struct platform_driver a500_ec_leds_driver = { |
119 | .driver = { |
120 | .name = "acer-a500-iconia-leds" , |
121 | }, |
122 | .probe = a500_ec_leds_probe, |
123 | }; |
124 | module_platform_driver(a500_ec_leds_driver); |
125 | |
126 | MODULE_DESCRIPTION("LED driver for Acer Iconia Tab A500 Power Button" ); |
127 | MODULE_AUTHOR("Dmitry Osipenko <digetx@gmail.com>" ); |
128 | MODULE_ALIAS("platform:acer-a500-iconia-leds" ); |
129 | MODULE_LICENSE("GPL" ); |
130 | |