Skip to content

Commit 2ea6d07

Browse files
lumaggregkh
authored andcommitted
usb: typec: ucsi: add Lenovo Yoga C630 glue driver
The Lenovo Yoga C630 WOS laptop provides implements UCSI interface in the onboard EC. Add glue driver to interface the platform's UCSI implementation. Reviewed-by: Bryan O'Donoghue <[email protected]> Reviewed-by: Heikki Krogerus <[email protected]> Reviewed-by: Ilpo Järvinen <[email protected]> Signed-off-by: Dmitry Baryshkov <[email protected]> Link: https://lore.kernel.org/r/[email protected] Signed-off-by: Greg Kroah-Hartman <[email protected]>
1 parent 6694d31 commit 2ea6d07

File tree

3 files changed

+214
-0
lines changed

3 files changed

+214
-0
lines changed

drivers/usb/typec/ucsi/Kconfig

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -69,4 +69,13 @@ config UCSI_PMIC_GLINK
6969
To compile the driver as a module, choose M here: the module will be
7070
called ucsi_glink.
7171

72+
config UCSI_LENOVO_YOGA_C630
73+
tristate "UCSI Interface Driver for Lenovo Yoga C630"
74+
depends on EC_LENOVO_YOGA_C630
75+
help
76+
This driver enables UCSI support on the Lenovo Yoga C630 laptop.
77+
78+
To compile the driver as a module, choose M here: the module will be
79+
called ucsi_yoga_c630.
80+
7281
endif

drivers/usb/typec/ucsi/Makefile

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,3 +21,4 @@ obj-$(CONFIG_UCSI_ACPI) += ucsi_acpi.o
2121
obj-$(CONFIG_UCSI_CCG) += ucsi_ccg.o
2222
obj-$(CONFIG_UCSI_STM32G0) += ucsi_stm32g0.o
2323
obj-$(CONFIG_UCSI_PMIC_GLINK) += ucsi_glink.o
24+
obj-$(CONFIG_UCSI_LENOVO_YOGA_C630) += ucsi_yoga_c630.o
Lines changed: 204 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,204 @@
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/bitops.h>
10+
#include <linux/completion.h>
11+
#include <linux/container_of.h>
12+
#include <linux/module.h>
13+
#include <linux/notifier.h>
14+
#include <linux/string.h>
15+
#include <linux/platform_data/lenovo-yoga-c630.h>
16+
17+
#include "ucsi.h"
18+
19+
struct yoga_c630_ucsi {
20+
struct yoga_c630_ec *ec;
21+
struct ucsi *ucsi;
22+
struct notifier_block nb;
23+
struct completion complete;
24+
unsigned long flags;
25+
#define UCSI_C630_COMMAND_PENDING 0
26+
#define UCSI_C630_ACK_PENDING 1
27+
u16 version;
28+
};
29+
30+
static int yoga_c630_ucsi_read(struct ucsi *ucsi, unsigned int offset,
31+
void *val, size_t val_len)
32+
{
33+
struct yoga_c630_ucsi *uec = ucsi_get_drvdata(ucsi);
34+
u8 buf[YOGA_C630_UCSI_READ_SIZE];
35+
int ret;
36+
37+
ret = yoga_c630_ec_ucsi_read(uec->ec, buf);
38+
if (ret)
39+
return ret;
40+
41+
if (offset == UCSI_VERSION) {
42+
memcpy(val, &uec->version, min(val_len, sizeof(uec->version)));
43+
return 0;
44+
}
45+
46+
switch (offset) {
47+
case UCSI_CCI:
48+
memcpy(val, buf, min(val_len, YOGA_C630_UCSI_CCI_SIZE));
49+
return 0;
50+
case UCSI_MESSAGE_IN:
51+
memcpy(val, buf + YOGA_C630_UCSI_CCI_SIZE,
52+
min(val_len, YOGA_C630_UCSI_DATA_SIZE));
53+
return 0;
54+
default:
55+
return -EINVAL;
56+
}
57+
}
58+
59+
static int yoga_c630_ucsi_async_write(struct ucsi *ucsi, unsigned int offset,
60+
const void *val, size_t val_len)
61+
{
62+
struct yoga_c630_ucsi *uec = ucsi_get_drvdata(ucsi);
63+
64+
if (offset != UCSI_CONTROL ||
65+
val_len != YOGA_C630_UCSI_WRITE_SIZE)
66+
return -EINVAL;
67+
68+
return yoga_c630_ec_ucsi_write(uec->ec, val);
69+
}
70+
71+
static int yoga_c630_ucsi_sync_write(struct ucsi *ucsi, unsigned int offset,
72+
const void *val, size_t val_len)
73+
{
74+
struct yoga_c630_ucsi *uec = ucsi_get_drvdata(ucsi);
75+
bool ack = UCSI_COMMAND(*(u64 *)val) == UCSI_ACK_CC_CI;
76+
int ret;
77+
78+
if (ack)
79+
set_bit(UCSI_C630_ACK_PENDING, &uec->flags);
80+
else
81+
set_bit(UCSI_C630_COMMAND_PENDING, &uec->flags);
82+
83+
reinit_completion(&uec->complete);
84+
85+
ret = yoga_c630_ucsi_async_write(ucsi, offset, val, val_len);
86+
if (ret)
87+
goto out_clear_bit;
88+
89+
if (!wait_for_completion_timeout(&uec->complete, 5 * HZ))
90+
ret = -ETIMEDOUT;
91+
92+
out_clear_bit:
93+
if (ack)
94+
clear_bit(UCSI_C630_ACK_PENDING, &uec->flags);
95+
else
96+
clear_bit(UCSI_C630_COMMAND_PENDING, &uec->flags);
97+
98+
return ret;
99+
}
100+
101+
const struct ucsi_operations yoga_c630_ucsi_ops = {
102+
.read = yoga_c630_ucsi_read,
103+
.sync_write = yoga_c630_ucsi_sync_write,
104+
.async_write = yoga_c630_ucsi_async_write,
105+
};
106+
107+
static void yoga_c630_ucsi_notify_ucsi(struct yoga_c630_ucsi *uec, u32 cci)
108+
{
109+
if (UCSI_CCI_CONNECTOR(cci))
110+
ucsi_connector_change(uec->ucsi, UCSI_CCI_CONNECTOR(cci));
111+
112+
if (cci & UCSI_CCI_ACK_COMPLETE &&
113+
test_bit(UCSI_C630_ACK_PENDING, &uec->flags))
114+
complete(&uec->complete);
115+
116+
if (cci & UCSI_CCI_COMMAND_COMPLETE &&
117+
test_bit(UCSI_C630_COMMAND_PENDING, &uec->flags))
118+
complete(&uec->complete);
119+
}
120+
121+
static int yoga_c630_ucsi_notify(struct notifier_block *nb,
122+
unsigned long action, void *data)
123+
{
124+
struct yoga_c630_ucsi *uec = container_of(nb, struct yoga_c630_ucsi, nb);
125+
u32 cci;
126+
int ret;
127+
128+
switch (action) {
129+
case LENOVO_EC_EVENT_USB:
130+
case LENOVO_EC_EVENT_HPD:
131+
ucsi_connector_change(uec->ucsi, 1);
132+
return NOTIFY_OK;
133+
134+
case LENOVO_EC_EVENT_UCSI:
135+
ret = uec->ucsi->ops->read(uec->ucsi, UCSI_CCI, &cci, sizeof(cci));
136+
if (ret)
137+
return NOTIFY_DONE;
138+
139+
yoga_c630_ucsi_notify_ucsi(uec, cci);
140+
141+
return NOTIFY_OK;
142+
143+
default:
144+
return NOTIFY_DONE;
145+
}
146+
}
147+
148+
static int yoga_c630_ucsi_probe(struct auxiliary_device *adev,
149+
const struct auxiliary_device_id *id)
150+
{
151+
struct yoga_c630_ec *ec = adev->dev.platform_data;
152+
struct yoga_c630_ucsi *uec;
153+
int ret;
154+
155+
uec = devm_kzalloc(&adev->dev, sizeof(*uec), GFP_KERNEL);
156+
if (!uec)
157+
return -ENOMEM;
158+
159+
uec->ec = ec;
160+
init_completion(&uec->complete);
161+
uec->nb.notifier_call = yoga_c630_ucsi_notify;
162+
163+
uec->ucsi = ucsi_create(&adev->dev, &yoga_c630_ucsi_ops);
164+
if (IS_ERR(uec->ucsi))
165+
return PTR_ERR(uec->ucsi);
166+
167+
ucsi_set_drvdata(uec->ucsi, uec);
168+
169+
uec->version = yoga_c630_ec_ucsi_get_version(uec->ec);
170+
171+
auxiliary_set_drvdata(adev, uec);
172+
173+
ret = yoga_c630_ec_register_notify(ec, &uec->nb);
174+
if (ret)
175+
return ret;
176+
177+
return ucsi_register(uec->ucsi);
178+
}
179+
180+
static void yoga_c630_ucsi_remove(struct auxiliary_device *adev)
181+
{
182+
struct yoga_c630_ucsi *uec = auxiliary_get_drvdata(adev);
183+
184+
yoga_c630_ec_unregister_notify(uec->ec, &uec->nb);
185+
ucsi_unregister(uec->ucsi);
186+
}
187+
188+
static const struct auxiliary_device_id yoga_c630_ucsi_id_table[] = {
189+
{ .name = YOGA_C630_MOD_NAME "." YOGA_C630_DEV_UCSI, },
190+
{}
191+
};
192+
MODULE_DEVICE_TABLE(auxiliary, yoga_c630_ucsi_id_table);
193+
194+
static struct auxiliary_driver yoga_c630_ucsi_driver = {
195+
.name = YOGA_C630_DEV_UCSI,
196+
.id_table = yoga_c630_ucsi_id_table,
197+
.probe = yoga_c630_ucsi_probe,
198+
.remove = yoga_c630_ucsi_remove,
199+
};
200+
201+
module_auxiliary_driver(yoga_c630_ucsi_driver);
202+
203+
MODULE_DESCRIPTION("Lenovo Yoga C630 UCSI");
204+
MODULE_LICENSE("GPL");

0 commit comments

Comments
 (0)