1 | // SPDX-License-Identifier: BSD-2-Clause OR GPL-2.0-or-later |
2 | /* |
3 | * ENE KB3930 Embedded Controller Driver |
4 | * |
5 | * Copyright (C) 2020 Lubomir Rintel |
6 | */ |
7 | |
8 | #include <linux/delay.h> |
9 | #include <linux/gpio/consumer.h> |
10 | #include <linux/i2c.h> |
11 | #include <linux/mfd/core.h> |
12 | #include <linux/module.h> |
13 | #include <linux/reboot.h> |
14 | #include <linux/regmap.h> |
15 | |
16 | /* I2C registers that are multiplexing access to the EC RAM. */ |
17 | enum { |
18 | EC_DATA_IN = 0x00, |
19 | EC_RAM_OUT = 0x80, |
20 | EC_RAM_IN = 0x81, |
21 | }; |
22 | |
23 | /* EC RAM registers. */ |
24 | enum { |
25 | EC_MODEL = 0x30, |
26 | EC_VERSION_MAJ = 0x31, |
27 | EC_VERSION_MIN = 0x32, |
28 | }; |
29 | |
30 | struct kb3930 { |
31 | struct i2c_client *client; |
32 | struct regmap *ram_regmap; |
33 | struct gpio_descs *off_gpios; |
34 | }; |
35 | |
36 | static struct kb3930 *kb3930_power_off; |
37 | |
38 | #define EC_GPIO_WAVE 0 |
39 | #define EC_GPIO_OFF_MODE 1 |
40 | |
41 | #define EC_OFF_MODE_REBOOT 0 |
42 | #define EC_OFF_MODE_POWER 1 |
43 | |
44 | static void kb3930_off(struct kb3930 *ddata, int off_mode) |
45 | { |
46 | gpiod_direction_output(desc: ddata->off_gpios->desc[EC_GPIO_OFF_MODE], |
47 | value: off_mode); |
48 | |
49 | /* |
50 | * This creates a 10 Hz wave on EC_GPIO_WAVE that signals a |
51 | * shutdown request to the EC. Once the EC detects it, it will |
52 | * proceed to turn the power off or reset the board depending on |
53 | * the value of EC_GPIO_OFF_MODE. |
54 | */ |
55 | while (1) { |
56 | mdelay(50); |
57 | gpiod_direction_output(desc: ddata->off_gpios->desc[EC_GPIO_WAVE], value: 0); |
58 | mdelay(50); |
59 | gpiod_direction_output(desc: ddata->off_gpios->desc[EC_GPIO_WAVE], value: 1); |
60 | } |
61 | } |
62 | |
63 | static int kb3930_restart(struct notifier_block *this, |
64 | unsigned long mode, void *cmd) |
65 | { |
66 | kb3930_off(ddata: kb3930_power_off, EC_OFF_MODE_REBOOT); |
67 | return NOTIFY_DONE; |
68 | } |
69 | |
70 | static void kb3930_pm_power_off(void) |
71 | { |
72 | kb3930_off(ddata: kb3930_power_off, EC_OFF_MODE_POWER); |
73 | } |
74 | |
75 | static struct notifier_block kb3930_restart_nb = { |
76 | .notifier_call = kb3930_restart, |
77 | }; |
78 | |
79 | static const struct mfd_cell ariel_ec_cells[] = { |
80 | { .name = "dell-wyse-ariel-led" , }, |
81 | { .name = "dell-wyse-ariel-power" , }, |
82 | }; |
83 | |
84 | static int kb3930_ec_ram_reg_write(void *context, unsigned int reg, |
85 | unsigned int val) |
86 | { |
87 | struct kb3930 *ddata = context; |
88 | |
89 | return i2c_smbus_write_word_data(client: ddata->client, command: EC_RAM_OUT, |
90 | value: (val << 8) | reg); |
91 | } |
92 | |
93 | static int kb3930_ec_ram_reg_read(void *context, unsigned int reg, |
94 | unsigned int *val) |
95 | { |
96 | struct kb3930 *ddata = context; |
97 | int ret; |
98 | |
99 | ret = i2c_smbus_write_word_data(client: ddata->client, command: EC_RAM_IN, value: reg); |
100 | if (ret < 0) |
101 | return ret; |
102 | |
103 | ret = i2c_smbus_read_word_data(client: ddata->client, command: EC_DATA_IN); |
104 | if (ret < 0) |
105 | return ret; |
106 | |
107 | *val = ret >> 8; |
108 | return 0; |
109 | } |
110 | |
111 | static const struct regmap_config kb3930_ram_regmap_config = { |
112 | .name = "ec_ram" , |
113 | .reg_bits = 8, |
114 | .val_bits = 8, |
115 | .reg_stride = 1, |
116 | .max_register = 0xff, |
117 | .reg_write = kb3930_ec_ram_reg_write, |
118 | .reg_read = kb3930_ec_ram_reg_read, |
119 | .fast_io = false, |
120 | }; |
121 | |
122 | static int kb3930_probe(struct i2c_client *client) |
123 | { |
124 | struct device *dev = &client->dev; |
125 | struct device_node *np = dev->of_node; |
126 | struct kb3930 *ddata; |
127 | unsigned int model; |
128 | int ret; |
129 | |
130 | ddata = devm_kzalloc(dev, size: sizeof(*ddata), GFP_KERNEL); |
131 | if (!ddata) |
132 | return -ENOMEM; |
133 | |
134 | kb3930_power_off = ddata; |
135 | ddata->client = client; |
136 | i2c_set_clientdata(client, data: ddata); |
137 | |
138 | ddata->ram_regmap = devm_regmap_init(dev, NULL, ddata, |
139 | &kb3930_ram_regmap_config); |
140 | if (IS_ERR(ptr: ddata->ram_regmap)) |
141 | return PTR_ERR(ptr: ddata->ram_regmap); |
142 | |
143 | ret = regmap_read(map: ddata->ram_regmap, reg: EC_MODEL, val: &model); |
144 | if (ret < 0) |
145 | return ret; |
146 | |
147 | /* Currently we only support the cells present on Dell Ariel model. */ |
148 | if (model != 'J') { |
149 | dev_err(dev, "unknown board model: %02x\n" , model); |
150 | return -ENODEV; |
151 | } |
152 | |
153 | ret = devm_mfd_add_devices(dev, PLATFORM_DEVID_AUTO, |
154 | cells: ariel_ec_cells, |
155 | ARRAY_SIZE(ariel_ec_cells), |
156 | NULL, irq_base: 0, NULL); |
157 | if (ret) |
158 | return ret; |
159 | |
160 | if (of_property_read_bool(np, propname: "system-power-controller" )) { |
161 | ddata->off_gpios = |
162 | devm_gpiod_get_array_optional(dev, con_id: "off" , flags: GPIOD_IN); |
163 | if (IS_ERR(ptr: ddata->off_gpios)) |
164 | return PTR_ERR(ptr: ddata->off_gpios); |
165 | if (ddata->off_gpios->ndescs < 2) { |
166 | dev_err(dev, "invalid off-gpios property\n" ); |
167 | return -EINVAL; |
168 | } |
169 | } |
170 | |
171 | if (ddata->off_gpios) { |
172 | register_restart_handler(&kb3930_restart_nb); |
173 | if (!pm_power_off) |
174 | pm_power_off = kb3930_pm_power_off; |
175 | } |
176 | |
177 | return 0; |
178 | } |
179 | |
180 | static void kb3930_remove(struct i2c_client *client) |
181 | { |
182 | struct kb3930 *ddata = i2c_get_clientdata(client); |
183 | |
184 | if (ddata->off_gpios) { |
185 | if (pm_power_off == kb3930_pm_power_off) |
186 | pm_power_off = NULL; |
187 | unregister_restart_handler(&kb3930_restart_nb); |
188 | } |
189 | kb3930_power_off = NULL; |
190 | } |
191 | |
192 | static const struct of_device_id kb3930_dt_ids[] = { |
193 | { .compatible = "ene,kb3930" }, |
194 | { } |
195 | }; |
196 | MODULE_DEVICE_TABLE(of, kb3930_dt_ids); |
197 | |
198 | static struct i2c_driver kb3930_driver = { |
199 | .probe = kb3930_probe, |
200 | .remove = kb3930_remove, |
201 | .driver = { |
202 | .name = "ene-kb3930" , |
203 | .of_match_table = kb3930_dt_ids, |
204 | }, |
205 | }; |
206 | module_i2c_driver(kb3930_driver); |
207 | |
208 | MODULE_AUTHOR("Lubomir Rintel <lkundrak@v3.sk>" ); |
209 | MODULE_DESCRIPTION("ENE KB3930 Embedded Controller Driver" ); |
210 | MODULE_LICENSE("Dual BSD/GPL" ); |
211 | |