Blame view

drivers/mfd/cros_ec.c 6.86 KB
4ab6174e8   Simon Glass   mfd: Add ChromeOS...
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
  /*
   * ChromeOS EC multi-function device
   *
   * Copyright (C) 2012 Google, Inc
   *
   * This software is licensed under the terms of the GNU General Public
   * License version 2, as published by the Free Software Foundation, and
   * may be copied, distributed, and modified under those terms.
   *
   * This program is distributed in the hope that it will be useful,
   * but WITHOUT ANY WARRANTY; without even the implied warranty of
   * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
   * GNU General Public License for more details.
   *
   * The ChromeOS EC multi function device is used to mux all the requests
   * to the EC device for its multiple features: keyboard controller,
   * battery charging and regulator control, firmware update.
   */
bb03ffb96   Todd Broch   mfd: cros_ec: Ins...
19
  #include <linux/of_platform.h>
4ab6174e8   Simon Glass   mfd: Add ChromeOS...
20
21
  #include <linux/interrupt.h>
  #include <linux/slab.h>
5ebeaff5c   Samuel Ortiz   mfd: cros_ec: Exp...
22
  #include <linux/module.h>
4ab6174e8   Simon Glass   mfd: Add ChromeOS...
23
24
  #include <linux/mfd/core.h>
  #include <linux/mfd/cros_ec.h>
f00c06fd9   Shawn Nematbakhsh   mfd: cros_ec: Sen...
25
  #include <linux/suspend.h>
6f1d912b6   Vic Yang   mfd: cros_ec: Add...
26
  #include <asm/unaligned.h>
a6551a76f   Andrew Bresticker   mfd: cros_ec: sto...
27

57b33ff07   Gwendal Grignou   mfd: cros_ec: Sup...
28
29
  #define CROS_EC_DEV_EC_INDEX 0
  #define CROS_EC_DEV_PD_INDEX 1
cf649e007   Lee Jones   mfd: cros_ec: Sta...
30
  static struct cros_ec_platform ec_p = {
57b33ff07   Gwendal Grignou   mfd: cros_ec: Sup...
31
32
33
  	.ec_name = CROS_EC_DEV_NAME,
  	.cmd_offset = EC_CMD_PASSTHRU_OFFSET(CROS_EC_DEV_EC_INDEX),
  };
cf649e007   Lee Jones   mfd: cros_ec: Sta...
34
  static struct cros_ec_platform pd_p = {
57b33ff07   Gwendal Grignou   mfd: cros_ec: Sup...
35
36
37
  	.ec_name = CROS_EC_DEV_PD_NAME,
  	.cmd_offset = EC_CMD_PASSTHRU_OFFSET(CROS_EC_DEV_PD_INDEX),
  };
cf649e007   Lee Jones   mfd: cros_ec: Sta...
38
  static const struct mfd_cell ec_cell = {
57b33ff07   Gwendal Grignou   mfd: cros_ec: Sup...
39
40
41
42
  	.name = "cros-ec-ctl",
  	.platform_data = &ec_p,
  	.pdata_size = sizeof(ec_p),
  };
cf649e007   Lee Jones   mfd: cros_ec: Sta...
43
  static const struct mfd_cell ec_pd_cell = {
57b33ff07   Gwendal Grignou   mfd: cros_ec: Sup...
44
45
46
  	.name = "cros-ec-ctl",
  	.platform_data = &pd_p,
  	.pdata_size = sizeof(pd_p),
4ab6174e8   Simon Glass   mfd: Add ChromeOS...
47
  };
6f1d912b6   Vic Yang   mfd: cros_ec: Add...
48
49
50
  static irqreturn_t ec_irq_thread(int irq, void *data)
  {
  	struct cros_ec_device *ec_dev = data;
29d99b966   Shawn Nematbakhsh   cros_ec: Don't si...
51
  	bool wake_event = true;
6f1d912b6   Vic Yang   mfd: cros_ec: Add...
52
  	int ret;
29d99b966   Shawn Nematbakhsh   cros_ec: Don't si...
53
54
55
56
57
58
59
60
  	ret = cros_ec_get_next_event(ec_dev, &wake_event);
  
  	/*
  	 * Signal only if wake host events or any interrupt if
  	 * cros_ec_get_next_event() returned an error (default value for
  	 * wake_event is true)
  	 */
  	if (wake_event && device_may_wakeup(ec_dev->dev))
6f1d912b6   Vic Yang   mfd: cros_ec: Add...
61
  		pm_wakeup_event(ec_dev->dev, 0);
6f1d912b6   Vic Yang   mfd: cros_ec: Add...
62
63
64
65
66
  	if (ret > 0)
  		blocking_notifier_call_chain(&ec_dev->event_notifier,
  					     0, ec_dev);
  	return IRQ_HANDLED;
  }
f00c06fd9   Shawn Nematbakhsh   mfd: cros_ec: Sen...
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
  static int cros_ec_sleep_event(struct cros_ec_device *ec_dev, u8 sleep_event)
  {
  	struct {
  		struct cros_ec_command msg;
  		struct ec_params_host_sleep_event req;
  	} __packed buf;
  
  	memset(&buf, 0, sizeof(buf));
  
  	buf.req.sleep_event = sleep_event;
  
  	buf.msg.command = EC_CMD_HOST_SLEEP_EVENT;
  	buf.msg.version = 0;
  	buf.msg.outsize = sizeof(buf.req);
  
  	return cros_ec_cmd_xfer(ec_dev, &buf.msg);
  }
4ab6174e8   Simon Glass   mfd: Add ChromeOS...
84
85
86
87
  int cros_ec_register(struct cros_ec_device *ec_dev)
  {
  	struct device *dev = ec_dev->dev;
  	int err = 0;
6f1d912b6   Vic Yang   mfd: cros_ec: Add...
88
  	BLOCKING_INIT_NOTIFIER_HEAD(&ec_dev->event_notifier);
2c7589af3   Stephen Barber   mfd: cros_ec: add...
89
90
91
92
93
94
95
96
97
98
99
  	ec_dev->max_request = sizeof(struct ec_params_hello);
  	ec_dev->max_response = sizeof(struct ec_response_get_protocol_info);
  	ec_dev->max_passthru = 0;
  
  	ec_dev->din = devm_kzalloc(dev, ec_dev->din_size, GFP_KERNEL);
  	if (!ec_dev->din)
  		return -ENOMEM;
  
  	ec_dev->dout = devm_kzalloc(dev, ec_dev->dout_size, GFP_KERNEL);
  	if (!ec_dev->dout)
  		return -ENOMEM;
4ab6174e8   Simon Glass   mfd: Add ChromeOS...
100

63427530f   Andrew Bresticker   mfd: cros_ec: mov...
101
  	mutex_init(&ec_dev->lock);
6648fdc71   Vincent Palatin   mfd: cros_ec: Fai...
102
103
104
105
106
107
  	err = cros_ec_query_all(ec_dev);
  	if (err) {
  		dev_err(dev, "Cannot identify the EC: error %d
  ", err);
  		return err;
  	}
2c7589af3   Stephen Barber   mfd: cros_ec: add...
108

6f1d912b6   Vic Yang   mfd: cros_ec: Add...
109
110
111
112
113
114
115
116
117
118
  	if (ec_dev->irq) {
  		err = request_threaded_irq(ec_dev->irq, NULL, ec_irq_thread,
  					   IRQF_TRIGGER_LOW | IRQF_ONESHOT,
  					   "chromeos-ec", ec_dev);
  		if (err) {
  			dev_err(dev, "Failed to request IRQ %d: %d",
  				ec_dev->irq, err);
  			return err;
  		}
  	}
57b33ff07   Gwendal Grignou   mfd: cros_ec: Sup...
119
  	err = mfd_add_devices(ec_dev->dev, PLATFORM_DEVID_AUTO, &ec_cell, 1,
4ab6174e8   Simon Glass   mfd: Add ChromeOS...
120
121
  			      NULL, ec_dev->irq, NULL);
  	if (err) {
57b33ff07   Gwendal Grignou   mfd: cros_ec: Sup...
122
123
124
125
  		dev_err(dev,
  			"Failed to register Embedded Controller subdevice %d
  ",
  			err);
6f1d912b6   Vic Yang   mfd: cros_ec: Add...
126
  		goto fail_mfd;
4ab6174e8   Simon Glass   mfd: Add ChromeOS...
127
  	}
57b33ff07   Gwendal Grignou   mfd: cros_ec: Sup...
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
  	if (ec_dev->max_passthru) {
  		/*
  		 * Register a PD device as well on top of this device.
  		 * We make the following assumptions:
  		 * - behind an EC, we have a pd
  		 * - only one device added.
  		 * - the EC is responsive at init time (it is not true for a
  		 *   sensor hub.
  		 */
  		err = mfd_add_devices(ec_dev->dev, PLATFORM_DEVID_AUTO,
  				      &ec_pd_cell, 1, NULL, ec_dev->irq, NULL);
  		if (err) {
  			dev_err(dev,
  				"Failed to register Power Delivery subdevice %d
  ",
  				err);
6f1d912b6   Vic Yang   mfd: cros_ec: Add...
144
  			goto fail_mfd;
57b33ff07   Gwendal Grignou   mfd: cros_ec: Sup...
145
146
  		}
  	}
bb03ffb96   Todd Broch   mfd: cros_ec: Ins...
147
  	if (IS_ENABLED(CONFIG_OF) && dev->of_node) {
2eb131efb   Benjamin Gaignard   mfd: cros_ec: Use...
148
  		err = devm_of_platform_populate(dev);
bb03ffb96   Todd Broch   mfd: cros_ec: Ins...
149
150
151
152
  		if (err) {
  			mfd_remove_devices(dev);
  			dev_err(dev, "Failed to register sub-devices
  ");
6f1d912b6   Vic Yang   mfd: cros_ec: Add...
153
  			goto fail_mfd;
bb03ffb96   Todd Broch   mfd: cros_ec: Ins...
154
155
  		}
  	}
f00c06fd9   Shawn Nematbakhsh   mfd: cros_ec: Sen...
156
157
158
159
160
161
162
163
  	/*
  	 * Clear sleep event - this will fail harmlessly on platforms that
  	 * don't implement the sleep event host command.
  	 */
  	err = cros_ec_sleep_event(ec_dev, 0);
  	if (err < 0)
  		dev_dbg(ec_dev->dev, "Error %d clearing sleep event to ec",
  			err);
533cec8f3   Bill Richardson   mfd: cros_ec: cle...
164
165
  	dev_info(dev, "Chrome EC device registered
  ");
4ab6174e8   Simon Glass   mfd: Add ChromeOS...
166

e04653a9d   Archana Patni   mfd: cros_ec: Add...
167
  	cros_ec_acpi_install_gpe_handler(dev);
4ab6174e8   Simon Glass   mfd: Add ChromeOS...
168
  	return 0;
6f1d912b6   Vic Yang   mfd: cros_ec: Add...
169
170
171
172
173
  
  fail_mfd:
  	if (ec_dev->irq)
  		free_irq(ec_dev->irq, ec_dev);
  	return err;
4ab6174e8   Simon Glass   mfd: Add ChromeOS...
174
  }
5ebeaff5c   Samuel Ortiz   mfd: cros_ec: Exp...
175
  EXPORT_SYMBOL(cros_ec_register);
4ab6174e8   Simon Glass   mfd: Add ChromeOS...
176
177
178
179
  
  int cros_ec_remove(struct cros_ec_device *ec_dev)
  {
  	mfd_remove_devices(ec_dev->dev);
4ab6174e8   Simon Glass   mfd: Add ChromeOS...
180

e04653a9d   Archana Patni   mfd: cros_ec: Add...
181
  	cros_ec_acpi_remove_gpe_handler();
f58b14e66   Jeffy Chen   mfd: cros_ec: Fre...
182
183
  	if (ec_dev->irq)
  		free_irq(ec_dev->irq, ec_dev);
4ab6174e8   Simon Glass   mfd: Add ChromeOS...
184
185
  	return 0;
  }
5ebeaff5c   Samuel Ortiz   mfd: cros_ec: Exp...
186
  EXPORT_SYMBOL(cros_ec_remove);
4ab6174e8   Simon Glass   mfd: Add ChromeOS...
187
188
189
190
191
  
  #ifdef CONFIG_PM_SLEEP
  int cros_ec_suspend(struct cros_ec_device *ec_dev)
  {
  	struct device *dev = ec_dev->dev;
f00c06fd9   Shawn Nematbakhsh   mfd: cros_ec: Sen...
192
193
  	int ret;
  	u8 sleep_event;
e04653a9d   Archana Patni   mfd: cros_ec: Add...
194
195
196
197
198
199
200
201
  	if (!IS_ENABLED(CONFIG_ACPI) || pm_suspend_via_firmware()) {
  		sleep_event = HOST_SLEEP_EVENT_S3_SUSPEND;
  	} else {
  		sleep_event = HOST_SLEEP_EVENT_S0IX_SUSPEND;
  
  		/* Clearing the GPE status for any pending event */
  		cros_ec_acpi_clear_gpe();
  	}
f00c06fd9   Shawn Nematbakhsh   mfd: cros_ec: Sen...
202
203
204
205
206
  
  	ret = cros_ec_sleep_event(ec_dev, sleep_event);
  	if (ret < 0)
  		dev_dbg(ec_dev->dev, "Error %d sending suspend event to ec",
  			ret);
4ab6174e8   Simon Glass   mfd: Add ChromeOS...
207
208
209
210
211
212
  
  	if (device_may_wakeup(dev))
  		ec_dev->wake_enabled = !enable_irq_wake(ec_dev->irq);
  
  	disable_irq(ec_dev->irq);
  	ec_dev->was_wake_device = ec_dev->wake_enabled;
a9eb186e1   Joseph Lo   mfd: cros_ec: Pre...
213
  	ec_dev->suspended = true;
4ab6174e8   Simon Glass   mfd: Add ChromeOS...
214
215
216
  
  	return 0;
  }
5ebeaff5c   Samuel Ortiz   mfd: cros_ec: Exp...
217
  EXPORT_SYMBOL(cros_ec_suspend);
4ab6174e8   Simon Glass   mfd: Add ChromeOS...
218

6f1d912b6   Vic Yang   mfd: cros_ec: Add...
219
220
  static void cros_ec_drain_events(struct cros_ec_device *ec_dev)
  {
29d99b966   Shawn Nematbakhsh   cros_ec: Don't si...
221
  	while (cros_ec_get_next_event(ec_dev, NULL) > 0)
6f1d912b6   Vic Yang   mfd: cros_ec: Add...
222
223
224
  		blocking_notifier_call_chain(&ec_dev->event_notifier,
  					     1, ec_dev);
  }
4ab6174e8   Simon Glass   mfd: Add ChromeOS...
225
226
  int cros_ec_resume(struct cros_ec_device *ec_dev)
  {
f00c06fd9   Shawn Nematbakhsh   mfd: cros_ec: Sen...
227
228
  	int ret;
  	u8 sleep_event;
a9eb186e1   Joseph Lo   mfd: cros_ec: Pre...
229
  	ec_dev->suspended = false;
4ab6174e8   Simon Glass   mfd: Add ChromeOS...
230
  	enable_irq(ec_dev->irq);
9c576bd35   Shawn Nematbakhsh   mfd: cros_ec: Sen...
231
232
233
  	sleep_event = (!IS_ENABLED(CONFIG_ACPI) || pm_suspend_via_firmware()) ?
  		      HOST_SLEEP_EVENT_S3_RESUME :
  		      HOST_SLEEP_EVENT_S0IX_RESUME;
f00c06fd9   Shawn Nematbakhsh   mfd: cros_ec: Sen...
234
235
236
237
238
  
  	ret = cros_ec_sleep_event(ec_dev, sleep_event);
  	if (ret < 0)
  		dev_dbg(ec_dev->dev, "Error %d sending resume event to ec",
  			ret);
6f1d912b6   Vic Yang   mfd: cros_ec: Add...
239
240
241
242
243
244
245
246
247
  	/*
  	 * In some cases, we need to distinguish between events that occur
  	 * during suspend if the EC is not a wake source. For example,
  	 * keypresses during suspend should be discarded if it does not wake
  	 * the system.
  	 *
  	 * If the EC is not a wake source, drain the event queue and mark them
  	 * as "queued during suspend".
  	 */
4ab6174e8   Simon Glass   mfd: Add ChromeOS...
248
249
250
  	if (ec_dev->wake_enabled) {
  		disable_irq_wake(ec_dev->irq);
  		ec_dev->wake_enabled = 0;
6f1d912b6   Vic Yang   mfd: cros_ec: Add...
251
252
  	} else {
  		cros_ec_drain_events(ec_dev);
4ab6174e8   Simon Glass   mfd: Add ChromeOS...
253
254
255
256
  	}
  
  	return 0;
  }
5ebeaff5c   Samuel Ortiz   mfd: cros_ec: Exp...
257
  EXPORT_SYMBOL(cros_ec_resume);
4ab6174e8   Simon Glass   mfd: Add ChromeOS...
258
  #endif
a865a5891   Bill Richardson   mfd: cros_ec: cle...
259
260
261
  
  MODULE_LICENSE("GPL");
  MODULE_DESCRIPTION("ChromeOS EC core driver");