Skip to content

Commit d2446f3

Browse files
committed
usb: typec: ucsi: Add support for Flipper One MCU UCSI controller
Flipper One is a handheld multi-tool device with an integrated MCU that exposes USB Type-C controller functionality among other things, following the UCSI specification. Add a driver for it. Signed-off-by: Alexey Charkov <alchark@flipper.net>
1 parent 861665c commit d2446f3

3 files changed

Lines changed: 187 additions & 0 deletions

File tree

drivers/usb/typec/ucsi/Kconfig

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -104,4 +104,15 @@ config UCSI_HUAWEI_GAOKUN
104104
To compile the driver as a module, choose M here: the module will be
105105
called ucsi_huawei_gaokun.
106106

107+
config UCSI_FLIPPER_ONE
108+
tristate "UCSI Interface Driver for Flipper One MCU"
109+
depends on MFD_FLIPPER_ONE
110+
help
111+
This driver enables UCSI support for the Type-C controller exposed
112+
by the Flipper One MCU, which presents the UCSI data structure as a
113+
register block in the shared MCU I2C register map.
114+
115+
To compile the driver as a module, choose M here: the module will be
116+
called ucsi_flipper_one.
117+
107118
endif

drivers/usb/typec/ucsi/Makefile

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,3 +28,4 @@ obj-$(CONFIG_UCSI_PMIC_GLINK) += ucsi_glink.o
2828
obj-$(CONFIG_CROS_EC_UCSI) += cros_ec_ucsi.o
2929
obj-$(CONFIG_UCSI_LENOVO_YOGA_C630) += ucsi_yoga_c630.o
3030
obj-$(CONFIG_UCSI_HUAWEI_GAOKUN) += ucsi_huawei_gaokun.o
31+
obj-$(CONFIG_UCSI_FLIPPER_ONE) += ucsi_flipper_one.o
Lines changed: 175 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,175 @@
1+
// SPDX-License-Identifier: GPL-2.0
2+
/*
3+
* UCSI driver for the Flipper One MCU Type-C controller
4+
* Copyright (C) 2026 Flipper FZCO
5+
*/
6+
7+
#include <linux/interrupt.h>
8+
#include <linux/mfd/flipper-one-mcu.h>
9+
#include <linux/module.h>
10+
#include <linux/platform_device.h>
11+
#include <linux/regmap.h>
12+
#include <linux/unaligned.h>
13+
14+
#include "ucsi.h"
15+
16+
struct flipper_one_ucsi {
17+
struct device *dev;
18+
struct regmap *regmap;
19+
struct ucsi *ucsi;
20+
int irq;
21+
};
22+
23+
/*
24+
* Read @len bytes from the UCSI register at MCU address @reg.
25+
*
26+
* regmap_raw_read() only accepts lengths that are a multiple of the register
27+
* width, but UCSI messages can have odd lengths, so round @len up into a bounce
28+
* buffer (the largest possible read is one message window)
29+
*/
30+
static int flipper_one_ucsi_read(struct ucsi *ucsi, unsigned int reg,
31+
void *val, size_t len)
32+
{
33+
struct flipper_one_ucsi *fo = ucsi_get_drvdata(ucsi);
34+
u8 buf[FOMCU_UCSI_MESSAGE_LEN];
35+
size_t aligned = round_up(len, regmap_get_val_bytes(fo->regmap));
36+
int ret;
37+
38+
if (aligned > sizeof(buf))
39+
return -EINVAL;
40+
41+
ret = regmap_raw_read(fo->regmap, reg, buf, aligned);
42+
if (ret)
43+
return ret;
44+
45+
memcpy(val, buf, len);
46+
return 0;
47+
}
48+
49+
static int flipper_one_ucsi_read_version(struct ucsi *ucsi, u16 *version)
50+
{
51+
return flipper_one_ucsi_read(ucsi, FOMCU_REG_UCSI_VERSION, version,
52+
sizeof(*version));
53+
}
54+
55+
static int flipper_one_ucsi_read_cci(struct ucsi *ucsi, u32 *cci)
56+
{
57+
return flipper_one_ucsi_read(ucsi, FOMCU_REG_UCSI_CCI, cci,
58+
sizeof(*cci));
59+
}
60+
61+
static int flipper_one_ucsi_read_message_in(struct ucsi *ucsi, void *val,
62+
size_t len)
63+
{
64+
return flipper_one_ucsi_read(ucsi, FOMCU_REG_UCSI_MESSAGE_IN, val, len);
65+
}
66+
67+
static int flipper_one_ucsi_async_control(struct ucsi *ucsi, u64 command)
68+
{
69+
struct flipper_one_ucsi *fo = ucsi_get_drvdata(ucsi);
70+
71+
return regmap_raw_write(fo->regmap, FOMCU_REG_UCSI_CONTROL,
72+
&command, sizeof(command));
73+
}
74+
75+
static const struct ucsi_operations flipper_one_ucsi_ops = {
76+
.read_version = flipper_one_ucsi_read_version,
77+
.read_cci = flipper_one_ucsi_read_cci,
78+
.poll_cci = flipper_one_ucsi_read_cci,
79+
.read_message_in = flipper_one_ucsi_read_message_in,
80+
.sync_control = ucsi_sync_control_common,
81+
.async_control = flipper_one_ucsi_async_control,
82+
};
83+
84+
static irqreturn_t flipper_one_ucsi_irq(int irq, void *data)
85+
{
86+
struct flipper_one_ucsi *fo = data;
87+
u32 cci;
88+
int ret;
89+
90+
ret = flipper_one_ucsi_read_cci(fo->ucsi, &cci);
91+
if (ret)
92+
return IRQ_NONE;
93+
94+
ucsi_notify_common(fo->ucsi, cci);
95+
96+
return IRQ_HANDLED;
97+
}
98+
99+
static int flipper_one_ucsi_probe(struct platform_device *pdev)
100+
{
101+
struct device *dev = &pdev->dev;
102+
struct flipper_one_ucsi *fo;
103+
int ret;
104+
105+
fo = devm_kzalloc(dev, sizeof(*fo), GFP_KERNEL);
106+
if (!fo)
107+
return -ENOMEM;
108+
109+
fo->dev = dev;
110+
111+
fo->regmap = dev_get_regmap(dev->parent, NULL);
112+
if (!fo->regmap)
113+
return dev_err_probe(dev, -ENODEV, "Failed to get MCU regmap\n");
114+
115+
fo->irq = platform_get_irq(pdev, 0);
116+
if (fo->irq < 0)
117+
return fo->irq;
118+
119+
fo->ucsi = ucsi_create(dev, &flipper_one_ucsi_ops);
120+
if (IS_ERR(fo->ucsi))
121+
return PTR_ERR(fo->ucsi);
122+
123+
ucsi_set_drvdata(fo->ucsi, fo);
124+
platform_set_drvdata(pdev, fo);
125+
126+
ret = request_threaded_irq(fo->irq, NULL, flipper_one_ucsi_irq,
127+
IRQF_ONESHOT, dev_name(dev), fo);
128+
if (ret) {
129+
dev_err_probe(dev, ret, "Failed to request IRQ\n");
130+
goto err_destroy;
131+
}
132+
133+
ret = ucsi_register(fo->ucsi);
134+
if (ret) {
135+
dev_err_probe(dev, ret, "Failed to register UCSI\n");
136+
goto err_free_irq;
137+
}
138+
139+
return 0;
140+
141+
err_free_irq:
142+
free_irq(fo->irq, fo);
143+
err_destroy:
144+
ucsi_destroy(fo->ucsi);
145+
return ret;
146+
}
147+
148+
static void flipper_one_ucsi_remove(struct platform_device *pdev)
149+
{
150+
struct flipper_one_ucsi *fo = platform_get_drvdata(pdev);
151+
152+
ucsi_unregister(fo->ucsi);
153+
free_irq(fo->irq, fo);
154+
ucsi_destroy(fo->ucsi);
155+
}
156+
157+
static const struct platform_device_id flipper_one_ucsi_ids[] = {
158+
{ "flipper-one-typec" },
159+
{}
160+
};
161+
MODULE_DEVICE_TABLE(platform, flipper_one_ucsi_ids);
162+
163+
static struct platform_driver flipper_one_ucsi_driver = {
164+
.driver = {
165+
.name = "flipper-one-typec",
166+
},
167+
.probe = flipper_one_ucsi_probe,
168+
.remove = flipper_one_ucsi_remove,
169+
.id_table = flipper_one_ucsi_ids,
170+
};
171+
module_platform_driver(flipper_one_ucsi_driver);
172+
173+
MODULE_DESCRIPTION("UCSI driver for Flipper One MCU Type-C controller");
174+
MODULE_AUTHOR("Alexey Charkov <alchark@flipper.net>");
175+
MODULE_LICENSE("GPL");

0 commit comments

Comments
 (0)