]>
Commit | Line | Data |
---|---|---|
eceae583 JN |
1 | // SPDX-License-Identifier: GPL-2.0-or-later |
2 | /* | |
3 | * The Netronix embedded controller is a microcontroller found in some | |
4 | * e-book readers designed by the original design manufacturer Netronix, Inc. | |
5 | * It contains RTC, battery monitoring, system power management, and PWM | |
6 | * functionality. | |
7 | * | |
8 | * This driver implements register access, version detection, and system | |
9 | * power-off/reset. | |
10 | * | |
11 | * Copyright 2020 Jonathan Neuschäfer <j.neuschaefer@gmx.net> | |
12 | */ | |
13 | ||
14 | #include <linux/delay.h> | |
15 | #include <linux/errno.h> | |
16 | #include <linux/i2c.h> | |
17 | #include <linux/mfd/core.h> | |
18 | #include <linux/mfd/ntxec.h> | |
19 | #include <linux/module.h> | |
20 | #include <linux/pm.h> | |
21 | #include <linux/reboot.h> | |
22 | #include <linux/regmap.h> | |
23 | #include <linux/types.h> | |
24 | #include <asm/unaligned.h> | |
25 | ||
26 | #define NTXEC_REG_VERSION 0x00 | |
27 | #define NTXEC_REG_POWEROFF 0x50 | |
28 | #define NTXEC_REG_POWERKEEP 0x70 | |
29 | #define NTXEC_REG_RESET 0x90 | |
30 | ||
31 | #define NTXEC_POWEROFF_VALUE 0x0100 | |
32 | #define NTXEC_POWERKEEP_VALUE 0x0800 | |
33 | #define NTXEC_RESET_VALUE 0xff00 | |
34 | ||
35 | static struct i2c_client *poweroff_restart_client; | |
36 | ||
37 | static void ntxec_poweroff(void) | |
38 | { | |
39 | int res; | |
40 | u8 buf[3] = { NTXEC_REG_POWEROFF }; | |
41 | struct i2c_msg msgs[] = { | |
42 | { | |
43 | .addr = poweroff_restart_client->addr, | |
44 | .flags = 0, | |
45 | .len = sizeof(buf), | |
46 | .buf = buf, | |
47 | }, | |
48 | }; | |
49 | ||
50 | put_unaligned_be16(NTXEC_POWEROFF_VALUE, buf + 1); | |
51 | ||
52 | res = i2c_transfer(poweroff_restart_client->adapter, msgs, ARRAY_SIZE(msgs)); | |
53 | if (res < 0) | |
54 | dev_warn(&poweroff_restart_client->dev, | |
55 | "Failed to power off (err = %d)\n", res); | |
56 | ||
57 | /* | |
58 | * The time from the register write until the host CPU is powered off | |
59 | * has been observed to be about 2.5 to 3 seconds. Sleep long enough to | |
60 | * safely avoid returning from the poweroff handler. | |
61 | */ | |
62 | msleep(5000); | |
63 | } | |
64 | ||
65 | static int ntxec_restart(struct notifier_block *nb, | |
66 | unsigned long action, void *data) | |
67 | { | |
68 | int res; | |
69 | u8 buf[3] = { NTXEC_REG_RESET }; | |
70 | /* | |
71 | * NOTE: The lower half of the reset value is not sent, because sending | |
72 | * it causes an I2C error. (The reset handler in the downstream driver | |
73 | * does send the full two-byte value, but doesn't check the result). | |
74 | */ | |
75 | struct i2c_msg msgs[] = { | |
76 | { | |
77 | .addr = poweroff_restart_client->addr, | |
78 | .flags = 0, | |
79 | .len = sizeof(buf) - 1, | |
80 | .buf = buf, | |
81 | }, | |
82 | }; | |
83 | ||
84 | put_unaligned_be16(NTXEC_RESET_VALUE, buf + 1); | |
85 | ||
86 | res = i2c_transfer(poweroff_restart_client->adapter, msgs, ARRAY_SIZE(msgs)); | |
87 | if (res < 0) | |
88 | dev_warn(&poweroff_restart_client->dev, | |
89 | "Failed to restart (err = %d)\n", res); | |
90 | ||
91 | return NOTIFY_DONE; | |
92 | } | |
93 | ||
94 | static struct notifier_block ntxec_restart_handler = { | |
95 | .notifier_call = ntxec_restart, | |
96 | .priority = 128, | |
97 | }; | |
98 | ||
d1157530 AK |
99 | static int regmap_ignore_write(void *context, |
100 | unsigned int reg, unsigned int val) | |
101 | ||
102 | { | |
103 | struct regmap *regmap = context; | |
104 | ||
105 | regmap_write(regmap, reg, val); | |
106 | ||
107 | return 0; | |
108 | } | |
109 | ||
110 | static int regmap_wrap_read(void *context, unsigned int reg, | |
111 | unsigned int *val) | |
112 | { | |
113 | struct regmap *regmap = context; | |
114 | ||
115 | return regmap_read(regmap, reg, val); | |
116 | } | |
117 | ||
118 | /* | |
119 | * Some firmware versions do not ack written data, add a wrapper. It | |
120 | * is used to stack another regmap on top. | |
121 | */ | |
122 | static const struct regmap_config regmap_config_noack = { | |
123 | .name = "ntxec_noack", | |
124 | .reg_bits = 8, | |
125 | .val_bits = 16, | |
126 | .cache_type = REGCACHE_NONE, | |
127 | .reg_write = regmap_ignore_write, | |
128 | .reg_read = regmap_wrap_read | |
129 | }; | |
130 | ||
eceae583 JN |
131 | static const struct regmap_config regmap_config = { |
132 | .name = "ntxec", | |
133 | .reg_bits = 8, | |
134 | .val_bits = 16, | |
135 | .cache_type = REGCACHE_NONE, | |
136 | .val_format_endian = REGMAP_ENDIAN_BIG, | |
137 | }; | |
138 | ||
d1157530 | 139 | static const struct mfd_cell ntxec_subdev[] = { |
eceae583 JN |
140 | { .name = "ntxec-rtc" }, |
141 | { .name = "ntxec-pwm" }, | |
142 | }; | |
143 | ||
d1157530 AK |
144 | static const struct mfd_cell ntxec_subdev_pwm[] = { |
145 | { .name = "ntxec-pwm" }, | |
146 | }; | |
147 | ||
eceae583 JN |
148 | static int ntxec_probe(struct i2c_client *client) |
149 | { | |
150 | struct ntxec *ec; | |
151 | unsigned int version; | |
152 | int res; | |
d1157530 AK |
153 | const struct mfd_cell *subdevs; |
154 | size_t n_subdevs; | |
eceae583 JN |
155 | |
156 | ec = devm_kmalloc(&client->dev, sizeof(*ec), GFP_KERNEL); | |
157 | if (!ec) | |
158 | return -ENOMEM; | |
159 | ||
160 | ec->dev = &client->dev; | |
161 | ||
162 | ec->regmap = devm_regmap_init_i2c(client, ®map_config); | |
163 | if (IS_ERR(ec->regmap)) { | |
164 | dev_err(ec->dev, "Failed to set up regmap for device\n"); | |
165 | return PTR_ERR(ec->regmap); | |
166 | } | |
167 | ||
168 | /* Determine the firmware version */ | |
169 | res = regmap_read(ec->regmap, NTXEC_REG_VERSION, &version); | |
170 | if (res < 0) { | |
171 | dev_err(ec->dev, "Failed to read firmware version number\n"); | |
172 | return res; | |
173 | } | |
174 | ||
175 | /* Bail out if we encounter an unknown firmware version */ | |
176 | switch (version) { | |
177 | case NTXEC_VERSION_KOBO_AURA: | |
d1157530 AK |
178 | subdevs = ntxec_subdev; |
179 | n_subdevs = ARRAY_SIZE(ntxec_subdev); | |
180 | break; | |
181 | case NTXEC_VERSION_TOLINO_SHINE2: | |
182 | subdevs = ntxec_subdev_pwm; | |
183 | n_subdevs = ARRAY_SIZE(ntxec_subdev_pwm); | |
184 | /* Another regmap stacked on top of the other */ | |
185 | ec->regmap = devm_regmap_init(ec->dev, NULL, | |
186 | ec->regmap, | |
187 | ®map_config_noack); | |
188 | if (IS_ERR(ec->regmap)) | |
189 | return PTR_ERR(ec->regmap); | |
eceae583 JN |
190 | break; |
191 | default: | |
192 | dev_err(ec->dev, | |
193 | "Netronix embedded controller version %04x is not supported.\n", | |
194 | version); | |
195 | return -ENODEV; | |
196 | } | |
197 | ||
198 | dev_info(ec->dev, | |
199 | "Netronix embedded controller version %04x detected.\n", version); | |
200 | ||
201 | if (of_device_is_system_power_controller(ec->dev->of_node)) { | |
202 | /* | |
203 | * Set the 'powerkeep' bit. This is necessary on some boards | |
204 | * in order to keep the system running. | |
205 | */ | |
206 | res = regmap_write(ec->regmap, NTXEC_REG_POWERKEEP, | |
207 | NTXEC_POWERKEEP_VALUE); | |
208 | if (res < 0) | |
209 | return res; | |
210 | ||
211 | if (poweroff_restart_client) | |
212 | /* | |
213 | * Another instance of the driver already took | |
214 | * poweroff/restart duties. | |
215 | */ | |
216 | dev_err(ec->dev, "poweroff_restart_client already assigned\n"); | |
217 | else | |
218 | poweroff_restart_client = client; | |
219 | ||
220 | if (pm_power_off) | |
221 | /* Another driver already registered a poweroff handler. */ | |
222 | dev_err(ec->dev, "pm_power_off already assigned\n"); | |
223 | else | |
224 | pm_power_off = ntxec_poweroff; | |
225 | ||
226 | res = register_restart_handler(&ntxec_restart_handler); | |
227 | if (res) | |
228 | dev_err(ec->dev, | |
229 | "Failed to register restart handler: %d\n", res); | |
230 | } | |
231 | ||
232 | i2c_set_clientdata(client, ec); | |
233 | ||
d1157530 AK |
234 | res = devm_mfd_add_devices(ec->dev, PLATFORM_DEVID_NONE, |
235 | subdevs, n_subdevs, NULL, 0, NULL); | |
eceae583 JN |
236 | if (res) |
237 | dev_err(ec->dev, "Failed to add subdevices: %d\n", res); | |
238 | ||
239 | return res; | |
240 | } | |
241 | ||
242 | static int ntxec_remove(struct i2c_client *client) | |
243 | { | |
244 | if (client == poweroff_restart_client) { | |
245 | poweroff_restart_client = NULL; | |
246 | pm_power_off = NULL; | |
247 | unregister_restart_handler(&ntxec_restart_handler); | |
248 | } | |
249 | ||
250 | return 0; | |
251 | } | |
252 | ||
253 | static const struct of_device_id of_ntxec_match_table[] = { | |
254 | { .compatible = "netronix,ntxec", }, | |
255 | {} | |
256 | }; | |
257 | MODULE_DEVICE_TABLE(of, of_ntxec_match_table); | |
258 | ||
259 | static struct i2c_driver ntxec_driver = { | |
260 | .driver = { | |
261 | .name = "ntxec", | |
262 | .of_match_table = of_ntxec_match_table, | |
263 | }, | |
264 | .probe_new = ntxec_probe, | |
265 | .remove = ntxec_remove, | |
266 | }; | |
267 | module_i2c_driver(ntxec_driver); | |
268 | ||
269 | MODULE_AUTHOR("Jonathan Neuschäfer <j.neuschaefer@gmx.net>"); | |
270 | MODULE_DESCRIPTION("Core driver for Netronix EC"); | |
271 | MODULE_LICENSE("GPL"); |