Linux kernel mirror (for testing) git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
kernel os linux
1
fork

Configure Feed

Select the types of activity you want to include in your feed.

at v4.18 261 lines 6.5 kB view raw
1/* 2 * ChromeOS EC multi-function device 3 * 4 * Copyright (C) 2012 Google, Inc 5 * 6 * This software is licensed under the terms of the GNU General Public 7 * License version 2, as published by the Free Software Foundation, and 8 * may be copied, distributed, and modified under those terms. 9 * 10 * This program is distributed in the hope that it will be useful, 11 * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 * GNU General Public License for more details. 14 * 15 * The ChromeOS EC multi function device is used to mux all the requests 16 * to the EC device for its multiple features: keyboard controller, 17 * battery charging and regulator control, firmware update. 18 */ 19 20#include <linux/of_platform.h> 21#include <linux/interrupt.h> 22#include <linux/slab.h> 23#include <linux/module.h> 24#include <linux/mfd/core.h> 25#include <linux/mfd/cros_ec.h> 26#include <linux/suspend.h> 27#include <asm/unaligned.h> 28 29#define CROS_EC_DEV_EC_INDEX 0 30#define CROS_EC_DEV_PD_INDEX 1 31 32static struct cros_ec_platform ec_p = { 33 .ec_name = CROS_EC_DEV_NAME, 34 .cmd_offset = EC_CMD_PASSTHRU_OFFSET(CROS_EC_DEV_EC_INDEX), 35}; 36 37static struct cros_ec_platform pd_p = { 38 .ec_name = CROS_EC_DEV_PD_NAME, 39 .cmd_offset = EC_CMD_PASSTHRU_OFFSET(CROS_EC_DEV_PD_INDEX), 40}; 41 42static const struct mfd_cell ec_cell = { 43 .name = "cros-ec-dev", 44 .platform_data = &ec_p, 45 .pdata_size = sizeof(ec_p), 46}; 47 48static const struct mfd_cell ec_pd_cell = { 49 .name = "cros-ec-dev", 50 .platform_data = &pd_p, 51 .pdata_size = sizeof(pd_p), 52}; 53 54static irqreturn_t ec_irq_thread(int irq, void *data) 55{ 56 struct cros_ec_device *ec_dev = data; 57 bool wake_event = true; 58 int ret; 59 60 ret = cros_ec_get_next_event(ec_dev, &wake_event); 61 62 /* 63 * Signal only if wake host events or any interrupt if 64 * cros_ec_get_next_event() returned an error (default value for 65 * wake_event is true) 66 */ 67 if (wake_event && device_may_wakeup(ec_dev->dev)) 68 pm_wakeup_event(ec_dev->dev, 0); 69 70 if (ret > 0) 71 blocking_notifier_call_chain(&ec_dev->event_notifier, 72 0, ec_dev); 73 return IRQ_HANDLED; 74} 75 76static int cros_ec_sleep_event(struct cros_ec_device *ec_dev, u8 sleep_event) 77{ 78 struct { 79 struct cros_ec_command msg; 80 struct ec_params_host_sleep_event req; 81 } __packed buf; 82 83 memset(&buf, 0, sizeof(buf)); 84 85 buf.req.sleep_event = sleep_event; 86 87 buf.msg.command = EC_CMD_HOST_SLEEP_EVENT; 88 buf.msg.version = 0; 89 buf.msg.outsize = sizeof(buf.req); 90 91 return cros_ec_cmd_xfer(ec_dev, &buf.msg); 92} 93 94int cros_ec_register(struct cros_ec_device *ec_dev) 95{ 96 struct device *dev = ec_dev->dev; 97 int err = 0; 98 99 BLOCKING_INIT_NOTIFIER_HEAD(&ec_dev->event_notifier); 100 101 ec_dev->max_request = sizeof(struct ec_params_hello); 102 ec_dev->max_response = sizeof(struct ec_response_get_protocol_info); 103 ec_dev->max_passthru = 0; 104 105 ec_dev->din = devm_kzalloc(dev, ec_dev->din_size, GFP_KERNEL); 106 if (!ec_dev->din) 107 return -ENOMEM; 108 109 ec_dev->dout = devm_kzalloc(dev, ec_dev->dout_size, GFP_KERNEL); 110 if (!ec_dev->dout) 111 return -ENOMEM; 112 113 mutex_init(&ec_dev->lock); 114 115 err = cros_ec_query_all(ec_dev); 116 if (err) { 117 dev_err(dev, "Cannot identify the EC: error %d\n", err); 118 return err; 119 } 120 121 if (ec_dev->irq) { 122 err = devm_request_threaded_irq(dev, ec_dev->irq, NULL, 123 ec_irq_thread, IRQF_TRIGGER_LOW | IRQF_ONESHOT, 124 "chromeos-ec", ec_dev); 125 if (err) { 126 dev_err(dev, "Failed to request IRQ %d: %d", 127 ec_dev->irq, err); 128 return err; 129 } 130 } 131 132 err = mfd_add_devices(ec_dev->dev, PLATFORM_DEVID_AUTO, &ec_cell, 1, 133 NULL, ec_dev->irq, NULL); 134 if (err) { 135 dev_err(dev, 136 "Failed to register Embedded Controller subdevice %d\n", 137 err); 138 return err; 139 } 140 141 if (ec_dev->max_passthru) { 142 /* 143 * Register a PD device as well on top of this device. 144 * We make the following assumptions: 145 * - behind an EC, we have a pd 146 * - only one device added. 147 * - the EC is responsive at init time (it is not true for a 148 * sensor hub. 149 */ 150 err = mfd_add_devices(ec_dev->dev, PLATFORM_DEVID_AUTO, 151 &ec_pd_cell, 1, NULL, ec_dev->irq, NULL); 152 if (err) { 153 dev_err(dev, 154 "Failed to register Power Delivery subdevice %d\n", 155 err); 156 return err; 157 } 158 } 159 160 if (IS_ENABLED(CONFIG_OF) && dev->of_node) { 161 err = devm_of_platform_populate(dev); 162 if (err) { 163 mfd_remove_devices(dev); 164 dev_err(dev, "Failed to register sub-devices\n"); 165 return err; 166 } 167 } 168 169 /* 170 * Clear sleep event - this will fail harmlessly on platforms that 171 * don't implement the sleep event host command. 172 */ 173 err = cros_ec_sleep_event(ec_dev, 0); 174 if (err < 0) 175 dev_dbg(ec_dev->dev, "Error %d clearing sleep event to ec", 176 err); 177 178 dev_info(dev, "Chrome EC device registered\n"); 179 180 return 0; 181} 182EXPORT_SYMBOL(cros_ec_register); 183 184int cros_ec_remove(struct cros_ec_device *ec_dev) 185{ 186 mfd_remove_devices(ec_dev->dev); 187 188 return 0; 189} 190EXPORT_SYMBOL(cros_ec_remove); 191 192#ifdef CONFIG_PM_SLEEP 193int cros_ec_suspend(struct cros_ec_device *ec_dev) 194{ 195 struct device *dev = ec_dev->dev; 196 int ret; 197 u8 sleep_event; 198 199 sleep_event = (!IS_ENABLED(CONFIG_ACPI) || pm_suspend_via_firmware()) ? 200 HOST_SLEEP_EVENT_S3_SUSPEND : 201 HOST_SLEEP_EVENT_S0IX_SUSPEND; 202 203 ret = cros_ec_sleep_event(ec_dev, sleep_event); 204 if (ret < 0) 205 dev_dbg(ec_dev->dev, "Error %d sending suspend event to ec", 206 ret); 207 208 if (device_may_wakeup(dev)) 209 ec_dev->wake_enabled = !enable_irq_wake(ec_dev->irq); 210 211 disable_irq(ec_dev->irq); 212 ec_dev->was_wake_device = ec_dev->wake_enabled; 213 ec_dev->suspended = true; 214 215 return 0; 216} 217EXPORT_SYMBOL(cros_ec_suspend); 218 219static void cros_ec_report_events_during_suspend(struct cros_ec_device *ec_dev) 220{ 221 while (cros_ec_get_next_event(ec_dev, NULL) > 0) 222 blocking_notifier_call_chain(&ec_dev->event_notifier, 223 1, ec_dev); 224} 225 226int cros_ec_resume(struct cros_ec_device *ec_dev) 227{ 228 int ret; 229 u8 sleep_event; 230 231 ec_dev->suspended = false; 232 enable_irq(ec_dev->irq); 233 234 sleep_event = (!IS_ENABLED(CONFIG_ACPI) || pm_suspend_via_firmware()) ? 235 HOST_SLEEP_EVENT_S3_RESUME : 236 HOST_SLEEP_EVENT_S0IX_RESUME; 237 238 ret = cros_ec_sleep_event(ec_dev, sleep_event); 239 if (ret < 0) 240 dev_dbg(ec_dev->dev, "Error %d sending resume event to ec", 241 ret); 242 243 if (ec_dev->wake_enabled) { 244 disable_irq_wake(ec_dev->irq); 245 ec_dev->wake_enabled = 0; 246 } 247 /* 248 * Let the mfd devices know about events that occur during 249 * suspend. This way the clients know what to do with them. 250 */ 251 cros_ec_report_events_during_suspend(ec_dev); 252 253 254 return 0; 255} 256EXPORT_SYMBOL(cros_ec_resume); 257 258#endif 259 260MODULE_LICENSE("GPL"); 261MODULE_DESCRIPTION("ChromeOS EC core driver");