File Index
Files and directories
Sign in
FILE

lenovo-yoga-c630.c

File preview with syntax highlighting.

linux-6.19.14/drivers/platform/arm64/lenovo-yoga-c630.c 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
 32struct yoga_c630_ec {
 33	struct i2c_client *client;
 34	struct mutex lock;
 35	struct blocking_notifier_head notifier_list;
 36};
 37
 38static 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
 54int 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}
 69EXPORT_SYMBOL_GPL(yoga_c630_ec_read8);
 70
 71int 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}
 96EXPORT_SYMBOL_GPL(yoga_c630_ec_read16);
 97
 98u16 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}
119EXPORT_SYMBOL_GPL(yoga_c630_ec_ucsi_get_version);
120
121int 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}
133EXPORT_SYMBOL_GPL(yoga_c630_ec_ucsi_write);
134
135int 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}
147EXPORT_SYMBOL_GPL(yoga_c630_ec_ucsi_read);
148
149static 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 */
174int 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}
178EXPORT_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 */
188void 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}
192EXPORT_SYMBOL_GPL(yoga_c630_ec_unregister_notify);
193
194static int yoga_c630_aux_init(struct device *parent, const char *name,
195			      struct yoga_c630_ec *ec)
196{
197	struct auxiliary_device *adev;
198
199	adev = devm_auxiliary_device_create(parent, name, ec);
200	if (!adev)
201		return -ENODEV;
202
203	return 0;
204}
205
206static int yoga_c630_ec_probe(struct i2c_client *client)
207{
208	struct device *dev = &client->dev;
209	struct yoga_c630_ec *ec;
210	int ret;
211
212	ec = devm_kzalloc(dev, sizeof(*ec), GFP_KERNEL);
213	if (!ec)
214		return -ENOMEM;
215
216	mutex_init(&ec->lock);
217	ec->client = client;
218	BLOCKING_INIT_NOTIFIER_HEAD(&ec->notifier_list);
219
220	ret = devm_request_threaded_irq(dev, client->irq,
221					NULL, yoga_c630_ec_thread_intr,
222					IRQF_ONESHOT, "yoga_c630_ec", ec);
223	if (ret < 0)
224		return dev_err_probe(dev, ret, "unable to request irq\n");
225
226	ret = yoga_c630_aux_init(dev, YOGA_C630_DEV_PSY, ec);
227	if (ret)
228		return ret;
229
230	return yoga_c630_aux_init(dev, YOGA_C630_DEV_UCSI, ec);
231}
232
233
234static const struct of_device_id yoga_c630_ec_of_match[] = {
235	{ .compatible = "lenovo,yoga-c630-ec" },
236	{}
237};
238MODULE_DEVICE_TABLE(of, yoga_c630_ec_of_match);
239
240static const struct i2c_device_id yoga_c630_ec_i2c_id_table[] = {
241	{ "yoga-c630-ec", },
242	{}
243};
244MODULE_DEVICE_TABLE(i2c, yoga_c630_ec_i2c_id_table);
245
246static struct i2c_driver yoga_c630_ec_i2c_driver = {
247	.driver = {
248		.name = "yoga-c630-ec",
249		.of_match_table = yoga_c630_ec_of_match
250	},
251	.probe = yoga_c630_ec_probe,
252	.id_table = yoga_c630_ec_i2c_id_table,
253};
254module_i2c_driver(yoga_c630_ec_i2c_driver);
255
256MODULE_DESCRIPTION("Lenovo Yoga C630 Embedded Controller");
257MODULE_LICENSE("GPL");
~/ Read-only file preview.