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, p: buf + 1); |
51 | |
52 | res = i2c_transfer(adap: 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(msecs: 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, p: buf + 1); |
85 | |
86 | res = i2c_transfer(adap: 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 | |
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(map: 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(map: 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 | |
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 | |
139 | static const struct mfd_cell ntxec_subdev[] = { |
140 | { .name = "ntxec-rtc" }, |
141 | { .name = "ntxec-pwm" }, |
142 | }; |
143 | |
144 | static const struct mfd_cell ntxec_subdev_pwm[] = { |
145 | { .name = "ntxec-pwm" }, |
146 | }; |
147 | |
148 | static int ntxec_probe(struct i2c_client *client) |
149 | { |
150 | struct ntxec *ec; |
151 | unsigned int version; |
152 | int res; |
153 | const struct mfd_cell *subdevs; |
154 | size_t n_subdevs; |
155 | |
156 | ec = devm_kmalloc(dev: &client->dev, size: 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(ptr: ec->regmap)) { |
164 | dev_err(ec->dev, "Failed to set up regmap for device\n" ); |
165 | return PTR_ERR(ptr: ec->regmap); |
166 | } |
167 | |
168 | /* Determine the firmware version */ |
169 | res = regmap_read(map: ec->regmap, NTXEC_REG_VERSION, val: &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: |
178 | case NTXEC_VERSION_TOLINO_VISION: |
179 | subdevs = ntxec_subdev; |
180 | n_subdevs = ARRAY_SIZE(ntxec_subdev); |
181 | break; |
182 | case NTXEC_VERSION_TOLINO_SHINE2: |
183 | subdevs = ntxec_subdev_pwm; |
184 | n_subdevs = ARRAY_SIZE(ntxec_subdev_pwm); |
185 | /* Another regmap stacked on top of the other */ |
186 | ec->regmap = devm_regmap_init(ec->dev, NULL, |
187 | ec->regmap, |
188 | ®map_config_noack); |
189 | if (IS_ERR(ptr: ec->regmap)) |
190 | return PTR_ERR(ptr: ec->regmap); |
191 | break; |
192 | default: |
193 | dev_err(ec->dev, |
194 | "Netronix embedded controller version %04x is not supported.\n" , |
195 | version); |
196 | return -ENODEV; |
197 | } |
198 | |
199 | dev_info(ec->dev, |
200 | "Netronix embedded controller version %04x detected.\n" , version); |
201 | |
202 | if (of_device_is_system_power_controller(np: ec->dev->of_node)) { |
203 | /* |
204 | * Set the 'powerkeep' bit. This is necessary on some boards |
205 | * in order to keep the system running. |
206 | */ |
207 | res = regmap_write(map: ec->regmap, NTXEC_REG_POWERKEEP, |
208 | NTXEC_POWERKEEP_VALUE); |
209 | if (res < 0) |
210 | return res; |
211 | |
212 | if (poweroff_restart_client) |
213 | /* |
214 | * Another instance of the driver already took |
215 | * poweroff/restart duties. |
216 | */ |
217 | dev_err(ec->dev, "poweroff_restart_client already assigned\n" ); |
218 | else |
219 | poweroff_restart_client = client; |
220 | |
221 | if (pm_power_off) |
222 | /* Another driver already registered a poweroff handler. */ |
223 | dev_err(ec->dev, "pm_power_off already assigned\n" ); |
224 | else |
225 | pm_power_off = ntxec_poweroff; |
226 | |
227 | res = register_restart_handler(&ntxec_restart_handler); |
228 | if (res) |
229 | dev_err(ec->dev, |
230 | "Failed to register restart handler: %d\n" , res); |
231 | } |
232 | |
233 | i2c_set_clientdata(client, data: ec); |
234 | |
235 | res = devm_mfd_add_devices(dev: ec->dev, PLATFORM_DEVID_NONE, |
236 | cells: subdevs, n_devs: n_subdevs, NULL, irq_base: 0, NULL); |
237 | if (res) |
238 | dev_err(ec->dev, "Failed to add subdevices: %d\n" , res); |
239 | |
240 | return res; |
241 | } |
242 | |
243 | static void ntxec_remove(struct i2c_client *client) |
244 | { |
245 | if (client == poweroff_restart_client) { |
246 | poweroff_restart_client = NULL; |
247 | pm_power_off = NULL; |
248 | unregister_restart_handler(&ntxec_restart_handler); |
249 | } |
250 | } |
251 | |
252 | static const struct of_device_id of_ntxec_match_table[] = { |
253 | { .compatible = "netronix,ntxec" , }, |
254 | {} |
255 | }; |
256 | MODULE_DEVICE_TABLE(of, of_ntxec_match_table); |
257 | |
258 | static struct i2c_driver ntxec_driver = { |
259 | .driver = { |
260 | .name = "ntxec" , |
261 | .of_match_table = of_ntxec_match_table, |
262 | }, |
263 | .probe = ntxec_probe, |
264 | .remove = ntxec_remove, |
265 | }; |
266 | module_i2c_driver(ntxec_driver); |
267 | |
268 | MODULE_AUTHOR("Jonathan Neuschäfer <j.neuschaefer@gmx.net>" ); |
269 | MODULE_DESCRIPTION("Core driver for Netronix EC" ); |
270 | MODULE_LICENSE("GPL" ); |
271 | |