Skip to content

Commit c5cf27d

Browse files
m1ng1109lag-linaro
authored andcommitted
i2c: Add Nuvoton NCT6694 I2C support
This driver supports I2C adapter functionality for NCT6694 MFD device based on USB interface. Each I2C controller uses the default baudrate of 100kHz, which can be overridden via module parameters. Acked-by: Andi Shyti <andi.shyti@kernel.org> Reviewed-by: Wolfram Sang <wsa+renesas@sang-engineering.com> Signed-off-by: Ming Yu <a0282524688@gmail.com> Link: https://lore.kernel.org/r/20250912091952.1169369-4-a0282524688@gmail.com Signed-off-by: Lee Jones <lee@kernel.org>
1 parent 611a995 commit c5cf27d

4 files changed

Lines changed: 208 additions & 0 deletions

File tree

MAINTAINERS

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18086,6 +18086,7 @@ NUVOTON NCT6694 MFD DRIVER
1808618086
M: Ming Yu <tmyu0@nuvoton.com>
1808718087
S: Supported
1808818088
F: drivers/gpio/gpio-nct6694.c
18089+
F: drivers/i2c/busses/i2c-nct6694.c
1808918090
F: drivers/mfd/nct6694.c
1809018091
F: include/linux/mfd/nct6694.h
1809118092

drivers/i2c/busses/Kconfig

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1357,6 +1357,16 @@ config I2C_LJCA
13571357
This driver can also be built as a module. If so, the module
13581358
will be called i2c-ljca.
13591359

1360+
config I2C_NCT6694
1361+
tristate "Nuvoton NCT6694 I2C adapter support"
1362+
depends on MFD_NCT6694
1363+
help
1364+
If you say yes to this option, support will be included for Nuvoton
1365+
NCT6694, a USB to I2C interface.
1366+
1367+
This driver can also be built as a module. If so, the module will
1368+
be called i2c-nct6694.
1369+
13601370
config I2C_CP2615
13611371
tristate "Silicon Labs CP2615 USB sound card and I2C adapter"
13621372
depends on USB

drivers/i2c/busses/Makefile

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -135,6 +135,7 @@ obj-$(CONFIG_I2C_GXP) += i2c-gxp.o
135135
obj-$(CONFIG_I2C_DIOLAN_U2C) += i2c-diolan-u2c.o
136136
obj-$(CONFIG_I2C_DLN2) += i2c-dln2.o
137137
obj-$(CONFIG_I2C_LJCA) += i2c-ljca.o
138+
obj-$(CONFIG_I2C_NCT6694) += i2c-nct6694.o
138139
obj-$(CONFIG_I2C_CP2615) += i2c-cp2615.o
139140
obj-$(CONFIG_I2C_PARPORT) += i2c-parport.o
140141
obj-$(CONFIG_I2C_PCI1XXXX) += i2c-mchp-pci1xxxx.o

drivers/i2c/busses/i2c-nct6694.c

Lines changed: 196 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,196 @@
1+
// SPDX-License-Identifier: GPL-2.0
2+
/*
3+
* Nuvoton NCT6694 I2C adapter driver based on USB interface.
4+
*
5+
* Copyright (C) 2025 Nuvoton Technology Corp.
6+
*/
7+
8+
#include <linux/i2c.h>
9+
#include <linux/idr.h>
10+
#include <linux/kernel.h>
11+
#include <linux/mfd/nct6694.h>
12+
#include <linux/module.h>
13+
#include <linux/platform_device.h>
14+
15+
/*
16+
* USB command module type for NCT6694 I2C controller.
17+
* This defines the module type used for communication with the NCT6694
18+
* I2C controller over the USB interface.
19+
*/
20+
#define NCT6694_I2C_MOD 0x03
21+
22+
/* Command 00h - I2C Deliver */
23+
#define NCT6694_I2C_DELIVER 0x00
24+
#define NCT6694_I2C_DELIVER_SEL 0x00
25+
26+
#define NCT6694_I2C_MAX_XFER_SIZE 64
27+
#define NCT6694_I2C_MAX_DEVS 6
28+
29+
static unsigned char br_reg[NCT6694_I2C_MAX_DEVS] = {[0 ... (NCT6694_I2C_MAX_DEVS - 1)] = 0xFF};
30+
31+
module_param_array(br_reg, byte, NULL, 0644);
32+
MODULE_PARM_DESC(br_reg,
33+
"I2C Baudrate register per adapter: (0=25K, 1=50K, 2=100K, 3=200K, 4=400K, 5=800K, 6=1M), default=2");
34+
35+
enum nct6694_i2c_baudrate {
36+
NCT6694_I2C_BR_25K = 0,
37+
NCT6694_I2C_BR_50K,
38+
NCT6694_I2C_BR_100K,
39+
NCT6694_I2C_BR_200K,
40+
NCT6694_I2C_BR_400K,
41+
NCT6694_I2C_BR_800K,
42+
NCT6694_I2C_BR_1M
43+
};
44+
45+
struct __packed nct6694_i2c_deliver {
46+
u8 port;
47+
u8 br;
48+
u8 addr;
49+
u8 w_cnt;
50+
u8 r_cnt;
51+
u8 rsv[11];
52+
u8 write_data[NCT6694_I2C_MAX_XFER_SIZE];
53+
u8 read_data[NCT6694_I2C_MAX_XFER_SIZE];
54+
};
55+
56+
struct nct6694_i2c_data {
57+
struct device *dev;
58+
struct nct6694 *nct6694;
59+
struct i2c_adapter adapter;
60+
struct nct6694_i2c_deliver deliver;
61+
unsigned char port;
62+
unsigned char br;
63+
};
64+
65+
static int nct6694_i2c_xfer(struct i2c_adapter *adap, struct i2c_msg *msgs, int num)
66+
{
67+
struct nct6694_i2c_data *data = adap->algo_data;
68+
struct nct6694_i2c_deliver *deliver = &data->deliver;
69+
static const struct nct6694_cmd_header cmd_hd = {
70+
.mod = NCT6694_I2C_MOD,
71+
.cmd = NCT6694_I2C_DELIVER,
72+
.sel = NCT6694_I2C_DELIVER_SEL,
73+
.len = cpu_to_le16(sizeof(*deliver))
74+
};
75+
int ret, i;
76+
77+
for (i = 0; i < num; i++) {
78+
struct i2c_msg *msg_temp = &msgs[i];
79+
80+
memset(deliver, 0, sizeof(*deliver));
81+
82+
deliver->port = data->port;
83+
deliver->br = data->br;
84+
deliver->addr = i2c_8bit_addr_from_msg(msg_temp);
85+
if (msg_temp->flags & I2C_M_RD) {
86+
deliver->r_cnt = msg_temp->len;
87+
ret = nct6694_write_msg(data->nct6694, &cmd_hd, deliver);
88+
if (ret < 0)
89+
return ret;
90+
91+
memcpy(msg_temp->buf, deliver->read_data, msg_temp->len);
92+
} else {
93+
deliver->w_cnt = msg_temp->len;
94+
memcpy(deliver->write_data, msg_temp->buf, msg_temp->len);
95+
ret = nct6694_write_msg(data->nct6694, &cmd_hd, deliver);
96+
if (ret < 0)
97+
return ret;
98+
}
99+
}
100+
101+
return num;
102+
}
103+
104+
static u32 nct6694_i2c_func(struct i2c_adapter *adapter)
105+
{
106+
return I2C_FUNC_I2C | I2C_FUNC_SMBUS_EMUL;
107+
}
108+
109+
static const struct i2c_adapter_quirks nct6694_i2c_quirks = {
110+
.max_read_len = NCT6694_I2C_MAX_XFER_SIZE,
111+
.max_write_len = NCT6694_I2C_MAX_XFER_SIZE,
112+
};
113+
114+
static const struct i2c_algorithm nct6694_i2c_algo = {
115+
.xfer = nct6694_i2c_xfer,
116+
.functionality = nct6694_i2c_func,
117+
};
118+
119+
static int nct6694_i2c_set_baudrate(struct nct6694_i2c_data *data)
120+
{
121+
if (data->port >= NCT6694_I2C_MAX_DEVS) {
122+
dev_err(data->dev, "Invalid I2C port index %d\n", data->port);
123+
return -EINVAL;
124+
}
125+
126+
if (br_reg[data->port] > NCT6694_I2C_BR_1M) {
127+
dev_warn(data->dev, "Invalid baudrate %d for I2C%d, using 100K\n",
128+
br_reg[data->port], data->port);
129+
br_reg[data->port] = NCT6694_I2C_BR_100K;
130+
}
131+
132+
data->br = br_reg[data->port];
133+
134+
return 0;
135+
}
136+
137+
static void nct6694_i2c_ida_free(void *d)
138+
{
139+
struct nct6694_i2c_data *data = d;
140+
struct nct6694 *nct6694 = data->nct6694;
141+
142+
ida_free(&nct6694->i2c_ida, data->port);
143+
}
144+
145+
static int nct6694_i2c_probe(struct platform_device *pdev)
146+
{
147+
struct device *dev = &pdev->dev;
148+
struct nct6694 *nct6694 = dev_get_drvdata(dev->parent);
149+
struct nct6694_i2c_data *data;
150+
int ret;
151+
152+
data = devm_kzalloc(dev, sizeof(*data), GFP_KERNEL);
153+
if (!data)
154+
return -ENOMEM;
155+
156+
data->dev = dev;
157+
data->nct6694 = nct6694;
158+
159+
ret = ida_alloc(&nct6694->i2c_ida, GFP_KERNEL);
160+
if (ret < 0)
161+
return ret;
162+
data->port = ret;
163+
164+
ret = devm_add_action_or_reset(dev, nct6694_i2c_ida_free, data);
165+
if (ret)
166+
return ret;
167+
168+
ret = nct6694_i2c_set_baudrate(data);
169+
if (ret)
170+
return ret;
171+
172+
sprintf(data->adapter.name, "NCT6694 I2C Adapter %d", data->port);
173+
data->adapter.owner = THIS_MODULE;
174+
data->adapter.algo = &nct6694_i2c_algo;
175+
data->adapter.quirks = &nct6694_i2c_quirks;
176+
data->adapter.dev.parent = dev;
177+
data->adapter.algo_data = data;
178+
179+
platform_set_drvdata(pdev, data);
180+
181+
return devm_i2c_add_adapter(dev, &data->adapter);
182+
}
183+
184+
static struct platform_driver nct6694_i2c_driver = {
185+
.driver = {
186+
.name = "nct6694-i2c",
187+
},
188+
.probe = nct6694_i2c_probe,
189+
};
190+
191+
module_platform_driver(nct6694_i2c_driver);
192+
193+
MODULE_DESCRIPTION("USB-I2C adapter driver for NCT6694");
194+
MODULE_AUTHOR("Ming Yu <tmyu0@nuvoton.com>");
195+
MODULE_LICENSE("GPL");
196+
MODULE_ALIAS("platform:nct6694-i2c");

0 commit comments

Comments
 (0)