Commit | Line | Data |
---|---|---|
ede6b2d1 LR |
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 | ||
5a2cf054 | 36 | static struct kb3930 *kb3930_power_off; |
ede6b2d1 LR |
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(ddata->off_gpios->desc[EC_GPIO_OFF_MODE], | |
47 | 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(ddata->off_gpios->desc[EC_GPIO_WAVE], 0); | |
58 | mdelay(50); | |
59 | gpiod_direction_output(ddata->off_gpios->desc[EC_GPIO_WAVE], 1); | |
60 | } | |
61 | } | |
62 | ||
63 | static int kb3930_restart(struct notifier_block *this, | |
64 | unsigned long mode, void *cmd) | |
65 | { | |
66 | kb3930_off(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(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(ddata->client, EC_RAM_OUT, | |
90 | (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(ddata->client, EC_RAM_IN, reg); | |
100 | if (ret < 0) | |
101 | return ret; | |
102 | ||
103 | ret = i2c_smbus_read_word_data(ddata->client, 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, 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, ddata); | |
137 | ||
138 | ddata->ram_regmap = devm_regmap_init(dev, NULL, ddata, | |
139 | &kb3930_ram_regmap_config); | |
140 | if (IS_ERR(ddata->ram_regmap)) | |
141 | return PTR_ERR(ddata->ram_regmap); | |
142 | ||
143 | ret = regmap_read(ddata->ram_regmap, EC_MODEL, &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 | ariel_ec_cells, | |
155 | ARRAY_SIZE(ariel_ec_cells), | |
156 | NULL, 0, NULL); | |
157 | if (ret) | |
158 | return ret; | |
159 | ||
160 | if (of_property_read_bool(np, "system-power-controller")) { | |
161 | ddata->off_gpios = | |
162 | devm_gpiod_get_array_optional(dev, "off", GPIOD_IN); | |
163 | if (IS_ERR(ddata->off_gpios)) | |
164 | return PTR_ERR(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 int 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 | return 0; | |
192 | } | |
193 | ||
194 | static const struct of_device_id kb3930_dt_ids[] = { | |
195 | { .compatible = "ene,kb3930" }, | |
196 | { } | |
197 | }; | |
198 | MODULE_DEVICE_TABLE(of, kb3930_dt_ids); | |
199 | ||
200 | static struct i2c_driver kb3930_driver = { | |
201 | .probe_new = kb3930_probe, | |
202 | .remove = kb3930_remove, | |
203 | .driver = { | |
204 | .name = "ene-kb3930", | |
e9063fee | 205 | .of_match_table = kb3930_dt_ids, |
ede6b2d1 LR |
206 | }, |
207 | }; | |
208 | module_i2c_driver(kb3930_driver); | |
209 | ||
210 | MODULE_AUTHOR("Lubomir Rintel <lkundrak@v3.sk>"); | |
211 | MODULE_DESCRIPTION("ENE KB3930 Embedded Controller Driver"); | |
212 | MODULE_LICENSE("Dual BSD/GPL"); |