Commit | Line | Data |
---|---|---|
1ece06ac DO |
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(led->rmap, 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(pdev->dev.parent, "KB930"); | |
72 | if (!rmap) | |
73 | return -EINVAL; | |
74 | ||
75 | /* reset and turn off LEDs */ | |
76 | regmap_multi_reg_write(rmap, a500_ec_leds_reset_seq, 2); | |
77 | ||
78 | white_led = devm_kzalloc(&pdev->dev, 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(&pdev->dev, 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(&pdev->dev, &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(&pdev->dev, &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"); |