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

platform: arm64: add Lenovo Yoga C630 WOS EC driver

Lenovo Yoga C630 WOS is a laptop using Snapdragon 850 SoC. Like many
laptops it uses an embedded controller (EC) to perform various platform
operations, including, but not limited, to Type-C port control or power
supply handlng.

Add the driver for the EC, that creates devices for UCSI and power
supply devices.

Reviewed-by: Bryan O'Donoghue <bryan.odonoghue@linaro.org>
Signed-off-by: Dmitry Baryshkov <dmitry.baryshkov@linaro.org>
Link: https://lore.kernel.org/r/20240614-yoga-ec-driver-v7-2-9f0b9b40ae76@linaro.org
[ij: added #include <linux/cleanup.h>]
Reviewed-by: Ilpo Järvinen <ilpo.jarvinen@linux.intel.com>
Signed-off-by: Ilpo Järvinen <ilpo.jarvinen@linux.intel.com>

authored by

Dmitry Baryshkov and committed by
Ilpo Järvinen
5e5f2f92 5958448d

+350
+14
drivers/platform/arm64/Kconfig
··· 32 32 laptop where this information is not properly exposed via the 33 33 standard ACPI devices. 34 34 35 + config EC_LENOVO_YOGA_C630 36 + tristate "Lenovo Yoga C630 Embedded Controller driver" 37 + depends on I2C 38 + help 39 + Driver for the Embedded Controller in the Qualcomm Snapdragon-based 40 + Lenovo Yoga C630, which provides battery and power adapter 41 + information. 42 + 43 + This driver provides battery and AC status support for the mentioned 44 + laptop where this information is not properly exposed via the 45 + standard ACPI devices. 46 + 47 + Say M or Y here to include this support. 48 + 35 49 endif # ARM64_PLATFORM_DEVICES
+1
drivers/platform/arm64/Makefile
··· 6 6 # 7 7 8 8 obj-$(CONFIG_EC_ACER_ASPIRE1) += acer-aspire1-ec.o 9 + obj-$(CONFIG_EC_LENOVO_YOGA_C630) += lenovo-yoga-c630.o
+291
drivers/platform/arm64/lenovo-yoga-c630.c
··· 1 + // SPDX-License-Identifier: GPL-2.0-only 2 + /* 3 + * Copyright (c) 2022-2024, Linaro Ltd 4 + * Authors: 5 + * Bjorn Andersson 6 + * Dmitry Baryshkov 7 + */ 8 + #include <linux/auxiliary_bus.h> 9 + #include <linux/cleanup.h> 10 + #include <linux/device.h> 11 + #include <linux/err.h> 12 + #include <linux/i2c.h> 13 + #include <linux/irqreturn.h> 14 + #include <linux/lockdep.h> 15 + #include <linux/module.h> 16 + #include <linux/mutex.h> 17 + #include <linux/notifier.h> 18 + #include <linux/slab.h> 19 + #include <linux/platform_data/lenovo-yoga-c630.h> 20 + 21 + #define LENOVO_EC_RESPONSE_REG 0x01 22 + #define LENOVO_EC_REQUEST_REG 0x02 23 + 24 + #define LENOVO_EC_UCSI_WRITE 0x20 25 + #define LENOVO_EC_UCSI_READ 0x21 26 + 27 + #define LENOVO_EC_READ_REG 0xb0 28 + #define LENOVO_EC_REQUEST_NEXT_EVENT 0x84 29 + 30 + #define LENOVO_EC_UCSI_VERSION 0x20 31 + 32 + struct yoga_c630_ec { 33 + struct i2c_client *client; 34 + struct mutex lock; 35 + struct blocking_notifier_head notifier_list; 36 + }; 37 + 38 + static int yoga_c630_ec_request(struct yoga_c630_ec *ec, u8 *req, size_t req_len, 39 + u8 *resp, size_t resp_len) 40 + { 41 + int ret; 42 + 43 + lockdep_assert_held(&ec->lock); 44 + 45 + ret = i2c_smbus_write_i2c_block_data(ec->client, LENOVO_EC_REQUEST_REG, 46 + req_len, req); 47 + if (ret < 0) 48 + return ret; 49 + 50 + return i2c_smbus_read_i2c_block_data(ec->client, LENOVO_EC_RESPONSE_REG, 51 + resp_len, resp); 52 + } 53 + 54 + int yoga_c630_ec_read8(struct yoga_c630_ec *ec, u8 addr) 55 + { 56 + u8 req[2] = { LENOVO_EC_READ_REG, }; 57 + int ret; 58 + u8 val; 59 + 60 + guard(mutex)(&ec->lock); 61 + 62 + req[1] = addr; 63 + ret = yoga_c630_ec_request(ec, req, sizeof(req), &val, 1); 64 + if (ret < 0) 65 + return ret; 66 + 67 + return val; 68 + } 69 + EXPORT_SYMBOL_GPL(yoga_c630_ec_read8); 70 + 71 + int yoga_c630_ec_read16(struct yoga_c630_ec *ec, u8 addr) 72 + { 73 + u8 req[2] = { LENOVO_EC_READ_REG, }; 74 + int ret; 75 + u8 msb; 76 + u8 lsb; 77 + 78 + /* don't overflow the address */ 79 + if (addr == 0xff) 80 + return -EINVAL; 81 + 82 + guard(mutex)(&ec->lock); 83 + 84 + req[1] = addr; 85 + ret = yoga_c630_ec_request(ec, req, sizeof(req), &lsb, 1); 86 + if (ret < 0) 87 + return ret; 88 + 89 + req[1] = addr + 1; 90 + ret = yoga_c630_ec_request(ec, req, sizeof(req), &msb, 1); 91 + if (ret < 0) 92 + return ret; 93 + 94 + return msb << 8 | lsb; 95 + } 96 + EXPORT_SYMBOL_GPL(yoga_c630_ec_read16); 97 + 98 + u16 yoga_c630_ec_ucsi_get_version(struct yoga_c630_ec *ec) 99 + { 100 + u8 req[3] = { 0xb3, 0xf2, }; 101 + int ret; 102 + u8 msb; 103 + u8 lsb; 104 + 105 + guard(mutex)(&ec->lock); 106 + 107 + req[2] = LENOVO_EC_UCSI_VERSION; 108 + ret = yoga_c630_ec_request(ec, req, sizeof(req), &lsb, 1); 109 + if (ret < 0) 110 + return ret; 111 + 112 + req[2] = LENOVO_EC_UCSI_VERSION + 1; 113 + ret = yoga_c630_ec_request(ec, req, sizeof(req), &msb, 1); 114 + if (ret < 0) 115 + return ret; 116 + 117 + return msb << 8 | lsb; 118 + } 119 + EXPORT_SYMBOL_GPL(yoga_c630_ec_ucsi_get_version); 120 + 121 + int yoga_c630_ec_ucsi_write(struct yoga_c630_ec *ec, 122 + const u8 req[YOGA_C630_UCSI_WRITE_SIZE]) 123 + { 124 + int ret; 125 + 126 + mutex_lock(&ec->lock); 127 + ret = i2c_smbus_write_i2c_block_data(ec->client, LENOVO_EC_UCSI_WRITE, 128 + YOGA_C630_UCSI_WRITE_SIZE, req); 129 + mutex_unlock(&ec->lock); 130 + 131 + return ret < 0 ? ret : 0; 132 + } 133 + EXPORT_SYMBOL_GPL(yoga_c630_ec_ucsi_write); 134 + 135 + int yoga_c630_ec_ucsi_read(struct yoga_c630_ec *ec, 136 + u8 resp[YOGA_C630_UCSI_READ_SIZE]) 137 + { 138 + int ret; 139 + 140 + mutex_lock(&ec->lock); 141 + ret = i2c_smbus_read_i2c_block_data(ec->client, LENOVO_EC_UCSI_READ, 142 + YOGA_C630_UCSI_READ_SIZE, resp); 143 + mutex_unlock(&ec->lock); 144 + 145 + return ret < 0 ? ret : 0; 146 + } 147 + EXPORT_SYMBOL_GPL(yoga_c630_ec_ucsi_read); 148 + 149 + static irqreturn_t yoga_c630_ec_thread_intr(int irq, void *data) 150 + { 151 + u8 req[] = { LENOVO_EC_REQUEST_NEXT_EVENT }; 152 + struct yoga_c630_ec *ec = data; 153 + u8 event; 154 + int ret; 155 + 156 + mutex_lock(&ec->lock); 157 + ret = yoga_c630_ec_request(ec, req, sizeof(req), &event, 1); 158 + mutex_unlock(&ec->lock); 159 + if (ret < 0) 160 + return IRQ_HANDLED; 161 + 162 + blocking_notifier_call_chain(&ec->notifier_list, event, ec); 163 + 164 + return IRQ_HANDLED; 165 + } 166 + 167 + /** 168 + * yoga_c630_ec_register_notify - Register a notifier callback for EC events. 169 + * @ec: Yoga C630 EC 170 + * @nb: Notifier block pointer to register 171 + * 172 + * Return: 0 on success or negative error code. 173 + */ 174 + int yoga_c630_ec_register_notify(struct yoga_c630_ec *ec, struct notifier_block *nb) 175 + { 176 + return blocking_notifier_chain_register(&ec->notifier_list, nb); 177 + } 178 + EXPORT_SYMBOL_GPL(yoga_c630_ec_register_notify); 179 + 180 + /** 181 + * yoga_c630_ec_unregister_notify - Unregister notifier callback for EC events. 182 + * @ec: Yoga C630 EC 183 + * @nb: Notifier block pointer to unregister 184 + * 185 + * Unregister a notifier callback that was previously registered with 186 + * yoga_c630_ec_register_notify(). 187 + */ 188 + void yoga_c630_ec_unregister_notify(struct yoga_c630_ec *ec, struct notifier_block *nb) 189 + { 190 + blocking_notifier_chain_unregister(&ec->notifier_list, nb); 191 + } 192 + EXPORT_SYMBOL_GPL(yoga_c630_ec_unregister_notify); 193 + 194 + static void yoga_c630_aux_release(struct device *dev) 195 + { 196 + struct auxiliary_device *adev = to_auxiliary_dev(dev); 197 + 198 + kfree(adev); 199 + } 200 + 201 + static void yoga_c630_aux_remove(void *data) 202 + { 203 + struct auxiliary_device *adev = data; 204 + 205 + auxiliary_device_delete(adev); 206 + auxiliary_device_uninit(adev); 207 + } 208 + 209 + static int yoga_c630_aux_init(struct device *parent, const char *name, 210 + struct yoga_c630_ec *ec) 211 + { 212 + struct auxiliary_device *adev; 213 + int ret; 214 + 215 + adev = kzalloc(sizeof(*adev), GFP_KERNEL); 216 + if (!adev) 217 + return -ENOMEM; 218 + 219 + adev->name = name; 220 + adev->id = 0; 221 + adev->dev.parent = parent; 222 + adev->dev.release = yoga_c630_aux_release; 223 + adev->dev.platform_data = ec; 224 + 225 + ret = auxiliary_device_init(adev); 226 + if (ret) { 227 + kfree(adev); 228 + return ret; 229 + } 230 + 231 + ret = auxiliary_device_add(adev); 232 + if (ret) { 233 + auxiliary_device_uninit(adev); 234 + return ret; 235 + } 236 + 237 + return devm_add_action_or_reset(parent, yoga_c630_aux_remove, adev); 238 + } 239 + 240 + static int yoga_c630_ec_probe(struct i2c_client *client) 241 + { 242 + struct device *dev = &client->dev; 243 + struct yoga_c630_ec *ec; 244 + int ret; 245 + 246 + ec = devm_kzalloc(dev, sizeof(*ec), GFP_KERNEL); 247 + if (!ec) 248 + return -ENOMEM; 249 + 250 + mutex_init(&ec->lock); 251 + ec->client = client; 252 + BLOCKING_INIT_NOTIFIER_HEAD(&ec->notifier_list); 253 + 254 + ret = devm_request_threaded_irq(dev, client->irq, 255 + NULL, yoga_c630_ec_thread_intr, 256 + IRQF_ONESHOT, "yoga_c630_ec", ec); 257 + if (ret < 0) 258 + return dev_err_probe(dev, ret, "unable to request irq\n"); 259 + 260 + ret = yoga_c630_aux_init(dev, YOGA_C630_DEV_PSY, ec); 261 + if (ret) 262 + return ret; 263 + 264 + return yoga_c630_aux_init(dev, YOGA_C630_DEV_UCSI, ec); 265 + } 266 + 267 + 268 + static const struct of_device_id yoga_c630_ec_of_match[] = { 269 + { .compatible = "lenovo,yoga-c630-ec" }, 270 + {} 271 + }; 272 + MODULE_DEVICE_TABLE(of, yoga_c630_ec_of_match); 273 + 274 + static const struct i2c_device_id yoga_c630_ec_i2c_id_table[] = { 275 + { "yoga-c630-ec", }, 276 + {} 277 + }; 278 + MODULE_DEVICE_TABLE(i2c, yoga_c630_ec_i2c_id_table); 279 + 280 + static struct i2c_driver yoga_c630_ec_i2c_driver = { 281 + .driver = { 282 + .name = "yoga-c630-ec", 283 + .of_match_table = yoga_c630_ec_of_match 284 + }, 285 + .probe = yoga_c630_ec_probe, 286 + .id_table = yoga_c630_ec_i2c_id_table, 287 + }; 288 + module_i2c_driver(yoga_c630_ec_i2c_driver); 289 + 290 + MODULE_DESCRIPTION("Lenovo Yoga C630 Embedded Controller"); 291 + MODULE_LICENSE("GPL");
+44
include/linux/platform_data/lenovo-yoga-c630.h
··· 1 + // SPDX-License-Identifier: GPL-2.0-only 2 + /* 3 + * Copyright (c) 2022-2024, Linaro Ltd 4 + * Authors: 5 + * Bjorn Andersson 6 + * Dmitry Baryshkov 7 + */ 8 + 9 + #ifndef _LENOVO_YOGA_C630_DATA_H 10 + #define _LENOVO_YOGA_C630_DATA_H 11 + 12 + struct yoga_c630_ec; 13 + struct notifier_block; 14 + 15 + #define YOGA_C630_MOD_NAME "lenovo_yoga_c630" 16 + 17 + #define YOGA_C630_DEV_UCSI "ucsi" 18 + #define YOGA_C630_DEV_PSY "psy" 19 + 20 + int yoga_c630_ec_read8(struct yoga_c630_ec *ec, u8 addr); 21 + int yoga_c630_ec_read16(struct yoga_c630_ec *ec, u8 addr); 22 + 23 + int yoga_c630_ec_register_notify(struct yoga_c630_ec *ec, struct notifier_block *nb); 24 + void yoga_c630_ec_unregister_notify(struct yoga_c630_ec *ec, struct notifier_block *nb); 25 + 26 + #define YOGA_C630_UCSI_WRITE_SIZE 8 27 + #define YOGA_C630_UCSI_CCI_SIZE 4 28 + #define YOGA_C630_UCSI_DATA_SIZE 16 29 + #define YOGA_C630_UCSI_READ_SIZE (YOGA_C630_UCSI_CCI_SIZE + YOGA_C630_UCSI_DATA_SIZE) 30 + 31 + u16 yoga_c630_ec_ucsi_get_version(struct yoga_c630_ec *ec); 32 + int yoga_c630_ec_ucsi_write(struct yoga_c630_ec *ec, 33 + const u8 req[YOGA_C630_UCSI_WRITE_SIZE]); 34 + int yoga_c630_ec_ucsi_read(struct yoga_c630_ec *ec, 35 + u8 resp[YOGA_C630_UCSI_READ_SIZE]); 36 + 37 + #define LENOVO_EC_EVENT_USB 0x20 38 + #define LENOVO_EC_EVENT_UCSI 0x21 39 + #define LENOVO_EC_EVENT_HPD 0x22 40 + #define LENOVO_EC_EVENT_BAT_STATUS 0x24 41 + #define LENOVO_EC_EVENT_BAT_INFO 0x25 42 + #define LENOVO_EC_EVENT_BAT_ADPT_STATUS 0x37 43 + 44 + #endif