File Index
Files and directories
Sign in
FILE

nct6694.c

File preview with syntax highlighting.

linux-6.19.14/drivers/mfd/nct6694.c C
  1// SPDX-License-Identifier: GPL-2.0
  2/*
  3 * Copyright (C) 2025 Nuvoton Technology Corp.
  4 *
  5 * Nuvoton NCT6694 core driver using USB interface to provide
  6 * access to the NCT6694 hardware monitoring and control features.
  7 *
  8 * The NCT6694 is an integrated controller that provides GPIO, I2C,
  9 * CAN, WDT, HWMON and RTC management.
 10 */
 11
 12#include <linux/bits.h>
 13#include <linux/interrupt.h>
 14#include <linux/idr.h>
 15#include <linux/irq.h>
 16#include <linux/irqdomain.h>
 17#include <linux/kernel.h>
 18#include <linux/mfd/core.h>
 19#include <linux/mfd/nct6694.h>
 20#include <linux/module.h>
 21#include <linux/slab.h>
 22#include <linux/spinlock.h>
 23#include <linux/usb.h>
 24
 25static const struct mfd_cell nct6694_devs[] = {
 26	MFD_CELL_NAME("nct6694-gpio"),
 27	MFD_CELL_NAME("nct6694-gpio"),
 28	MFD_CELL_NAME("nct6694-gpio"),
 29	MFD_CELL_NAME("nct6694-gpio"),
 30	MFD_CELL_NAME("nct6694-gpio"),
 31	MFD_CELL_NAME("nct6694-gpio"),
 32	MFD_CELL_NAME("nct6694-gpio"),
 33	MFD_CELL_NAME("nct6694-gpio"),
 34	MFD_CELL_NAME("nct6694-gpio"),
 35	MFD_CELL_NAME("nct6694-gpio"),
 36	MFD_CELL_NAME("nct6694-gpio"),
 37	MFD_CELL_NAME("nct6694-gpio"),
 38	MFD_CELL_NAME("nct6694-gpio"),
 39	MFD_CELL_NAME("nct6694-gpio"),
 40	MFD_CELL_NAME("nct6694-gpio"),
 41	MFD_CELL_NAME("nct6694-gpio"),
 42
 43	MFD_CELL_NAME("nct6694-i2c"),
 44	MFD_CELL_NAME("nct6694-i2c"),
 45	MFD_CELL_NAME("nct6694-i2c"),
 46	MFD_CELL_NAME("nct6694-i2c"),
 47	MFD_CELL_NAME("nct6694-i2c"),
 48	MFD_CELL_NAME("nct6694-i2c"),
 49
 50	MFD_CELL_NAME("nct6694-canfd"),
 51	MFD_CELL_NAME("nct6694-canfd"),
 52
 53	MFD_CELL_NAME("nct6694-wdt"),
 54	MFD_CELL_NAME("nct6694-wdt"),
 55
 56	MFD_CELL_NAME("nct6694-hwmon"),
 57
 58	MFD_CELL_NAME("nct6694-rtc"),
 59};
 60
 61static int nct6694_response_err_handling(struct nct6694 *nct6694, unsigned char err_status)
 62{
 63	switch (err_status) {
 64	case NCT6694_NO_ERROR:
 65		return 0;
 66	case NCT6694_NOT_SUPPORT_ERROR:
 67		dev_err(nct6694->dev, "Command is not supported!\n");
 68		break;
 69	case NCT6694_NO_RESPONSE_ERROR:
 70		dev_warn(nct6694->dev, "Command received no response!\n");
 71		break;
 72	case NCT6694_TIMEOUT_ERROR:
 73		dev_warn(nct6694->dev, "Command timed out!\n");
 74		break;
 75	case NCT6694_PENDING:
 76		dev_err(nct6694->dev, "Command is pending!\n");
 77		break;
 78	default:
 79		return -EINVAL;
 80	}
 81
 82	return -EIO;
 83}
 84
 85/**
 86 * nct6694_read_msg() - Read message from NCT6694 device
 87 * @nct6694: NCT6694 device pointer
 88 * @cmd_hd: command header structure
 89 * @buf: buffer to store the response data
 90 *
 91 * Sends a command to the NCT6694 device and reads the response.
 92 * The command header is specified in @cmd_hd, and the response
 93 * data is stored in @buf.
 94 *
 95 * Return: Negative value on error or 0 on success.
 96 */
 97int nct6694_read_msg(struct nct6694 *nct6694, const struct nct6694_cmd_header *cmd_hd, void *buf)
 98{
 99	union nct6694_usb_msg *msg = nct6694->usb_msg;
100	struct usb_device *udev = nct6694->udev;
101	int tx_len, rx_len, ret;
102
103	guard(mutex)(&nct6694->access_lock);
104
105	memcpy(&msg->cmd_header, cmd_hd, sizeof(*cmd_hd));
106	msg->cmd_header.hctrl = NCT6694_HCTRL_GET;
107
108	/* Send command packet to USB device */
109	ret = usb_bulk_msg(udev, usb_sndbulkpipe(udev, NCT6694_BULK_OUT_EP), &msg->cmd_header,
110			   sizeof(*msg), &tx_len, NCT6694_URB_TIMEOUT);
111	if (ret)
112		return ret;
113
114	/* Receive response packet from USB device */
115	ret = usb_bulk_msg(udev, usb_rcvbulkpipe(udev, NCT6694_BULK_IN_EP), &msg->response_header,
116			   sizeof(*msg), &rx_len, NCT6694_URB_TIMEOUT);
117	if (ret)
118		return ret;
119
120	/* Receive data packet from USB device */
121	ret = usb_bulk_msg(udev, usb_rcvbulkpipe(udev, NCT6694_BULK_IN_EP), buf,
122			   le16_to_cpu(cmd_hd->len), &rx_len, NCT6694_URB_TIMEOUT);
123	if (ret)
124		return ret;
125
126	if (rx_len != le16_to_cpu(cmd_hd->len)) {
127		dev_err(nct6694->dev, "Expected received length %d, but got %d\n",
128			le16_to_cpu(cmd_hd->len), rx_len);
129		return -EIO;
130	}
131
132	return nct6694_response_err_handling(nct6694, msg->response_header.sts);
133}
134EXPORT_SYMBOL_GPL(nct6694_read_msg);
135
136/**
137 * nct6694_write_msg() - Write message to NCT6694 device
138 * @nct6694: NCT6694 device pointer
139 * @cmd_hd: command header structure
140 * @buf: buffer containing the data to be sent
141 *
142 * Sends a command to the NCT6694 device and writes the data
143 * from @buf. The command header is specified in @cmd_hd.
144 *
145 * Return: Negative value on error or 0 on success.
146 */
147int nct6694_write_msg(struct nct6694 *nct6694, const struct nct6694_cmd_header *cmd_hd, void *buf)
148{
149	union nct6694_usb_msg *msg = nct6694->usb_msg;
150	struct usb_device *udev = nct6694->udev;
151	int tx_len, rx_len, ret;
152
153	guard(mutex)(&nct6694->access_lock);
154
155	memcpy(&msg->cmd_header, cmd_hd, sizeof(*cmd_hd));
156	msg->cmd_header.hctrl = NCT6694_HCTRL_SET;
157
158	/* Send command packet to USB device */
159	ret = usb_bulk_msg(udev, usb_sndbulkpipe(udev, NCT6694_BULK_OUT_EP), &msg->cmd_header,
160			   sizeof(*msg), &tx_len, NCT6694_URB_TIMEOUT);
161	if (ret)
162		return ret;
163
164	/* Send data packet to USB device */
165	ret = usb_bulk_msg(udev, usb_sndbulkpipe(udev, NCT6694_BULK_OUT_EP), buf,
166			   le16_to_cpu(cmd_hd->len), &tx_len, NCT6694_URB_TIMEOUT);
167	if (ret)
168		return ret;
169
170	/* Receive response packet from USB device */
171	ret = usb_bulk_msg(udev, usb_rcvbulkpipe(udev, NCT6694_BULK_IN_EP), &msg->response_header,
172			   sizeof(*msg), &rx_len, NCT6694_URB_TIMEOUT);
173	if (ret)
174		return ret;
175
176	/* Receive data packet from USB device */
177	ret = usb_bulk_msg(udev, usb_rcvbulkpipe(udev, NCT6694_BULK_IN_EP), buf,
178			   le16_to_cpu(cmd_hd->len), &rx_len, NCT6694_URB_TIMEOUT);
179	if (ret)
180		return ret;
181
182	if (rx_len != le16_to_cpu(cmd_hd->len)) {
183		dev_err(nct6694->dev, "Expected transmitted length %d, but got %d\n",
184			le16_to_cpu(cmd_hd->len), rx_len);
185		return -EIO;
186	}
187
188	return nct6694_response_err_handling(nct6694, msg->response_header.sts);
189}
190EXPORT_SYMBOL_GPL(nct6694_write_msg);
191
192static void usb_int_callback(struct urb *urb)
193{
194	struct nct6694 *nct6694 = urb->context;
195	__le32 *status_le = urb->transfer_buffer;
196	u32 int_status;
197	int ret;
198
199	switch (urb->status) {
200	case 0:
201		break;
202	case -ECONNRESET:
203	case -ENOENT:
204	case -ESHUTDOWN:
205		return;
206	default:
207		goto resubmit;
208	}
209
210	int_status = le32_to_cpu(*status_le);
211
212	while (int_status) {
213		int irq = __ffs(int_status);
214
215		generic_handle_irq_safe(irq_find_mapping(nct6694->domain, irq));
216		int_status &= ~BIT(irq);
217	}
218
219resubmit:
220	ret = usb_submit_urb(urb, GFP_ATOMIC);
221	if (ret)
222		dev_warn(nct6694->dev, "Failed to resubmit urb, status %pe",  ERR_PTR(ret));
223}
224
225static void nct6694_irq_enable(struct irq_data *data)
226{
227	struct nct6694 *nct6694 = irq_data_get_irq_chip_data(data);
228	irq_hw_number_t hwirq = irqd_to_hwirq(data);
229
230	guard(spinlock_irqsave)(&nct6694->irq_lock);
231
232	nct6694->irq_enable |= BIT(hwirq);
233}
234
235static void nct6694_irq_disable(struct irq_data *data)
236{
237	struct nct6694 *nct6694 = irq_data_get_irq_chip_data(data);
238	irq_hw_number_t hwirq = irqd_to_hwirq(data);
239
240	guard(spinlock_irqsave)(&nct6694->irq_lock);
241
242	nct6694->irq_enable &= ~BIT(hwirq);
243}
244
245static const struct irq_chip nct6694_irq_chip = {
246	.name = "nct6694-irq",
247	.flags = IRQCHIP_SKIP_SET_WAKE,
248	.irq_enable = nct6694_irq_enable,
249	.irq_disable = nct6694_irq_disable,
250};
251
252static int nct6694_irq_domain_map(struct irq_domain *d, unsigned int irq, irq_hw_number_t hw)
253{
254	struct nct6694 *nct6694 = d->host_data;
255
256	irq_set_chip_data(irq, nct6694);
257	irq_set_chip_and_handler(irq, &nct6694_irq_chip, handle_simple_irq);
258
259	return 0;
260}
261
262static void nct6694_irq_domain_unmap(struct irq_domain *d, unsigned int irq)
263{
264	irq_set_chip_and_handler(irq, NULL, NULL);
265	irq_set_chip_data(irq, NULL);
266}
267
268static const struct irq_domain_ops nct6694_irq_domain_ops = {
269	.map	= nct6694_irq_domain_map,
270	.unmap	= nct6694_irq_domain_unmap,
271};
272
273static int nct6694_usb_probe(struct usb_interface *iface,
274			     const struct usb_device_id *id)
275{
276	struct usb_device *udev = interface_to_usbdev(iface);
277	struct usb_endpoint_descriptor *int_endpoint;
278	struct usb_host_interface *interface;
279	struct device *dev = &iface->dev;
280	struct nct6694 *nct6694;
281	int ret;
282
283	nct6694 = devm_kzalloc(dev, sizeof(*nct6694), GFP_KERNEL);
284	if (!nct6694)
285		return -ENOMEM;
286
287	nct6694->usb_msg = devm_kzalloc(dev, sizeof(union nct6694_usb_msg), GFP_KERNEL);
288	if (!nct6694->usb_msg)
289		return -ENOMEM;
290
291	nct6694->int_buffer = devm_kzalloc(dev, sizeof(*nct6694->int_buffer), GFP_KERNEL);
292	if (!nct6694->int_buffer)
293		return -ENOMEM;
294
295	nct6694->int_in_urb = usb_alloc_urb(0, GFP_KERNEL);
296	if (!nct6694->int_in_urb)
297		return -ENOMEM;
298
299	nct6694->domain = irq_domain_create_simple(NULL, NCT6694_NR_IRQS, 0,
300						   &nct6694_irq_domain_ops,
301						   nct6694);
302	if (!nct6694->domain) {
303		ret = -ENODEV;
304		goto err_urb;
305	}
306
307	nct6694->dev = dev;
308	nct6694->udev = udev;
309
310	ida_init(&nct6694->gpio_ida);
311	ida_init(&nct6694->i2c_ida);
312	ida_init(&nct6694->canfd_ida);
313	ida_init(&nct6694->wdt_ida);
314
315	spin_lock_init(&nct6694->irq_lock);
316
317	ret = devm_mutex_init(dev, &nct6694->access_lock);
318	if (ret)
319		goto err_ida;
320
321	interface = iface->cur_altsetting;
322
323	int_endpoint = &interface->endpoint[0].desc;
324	if (!usb_endpoint_is_int_in(int_endpoint)) {
325		ret = -ENODEV;
326		goto err_ida;
327	}
328
329	usb_fill_int_urb(nct6694->int_in_urb, udev, usb_rcvintpipe(udev, NCT6694_INT_IN_EP),
330			 nct6694->int_buffer, sizeof(*nct6694->int_buffer), usb_int_callback,
331			 nct6694, int_endpoint->bInterval);
332
333	ret = usb_submit_urb(nct6694->int_in_urb, GFP_KERNEL);
334	if (ret)
335		goto err_ida;
336
337	usb_set_intfdata(iface, nct6694);
338
339	ret = mfd_add_hotplug_devices(dev, nct6694_devs, ARRAY_SIZE(nct6694_devs));
340	if (ret)
341		goto err_mfd;
342
343	return 0;
344
345err_mfd:
346	usb_kill_urb(nct6694->int_in_urb);
347err_ida:
348	ida_destroy(&nct6694->wdt_ida);
349	ida_destroy(&nct6694->canfd_ida);
350	ida_destroy(&nct6694->i2c_ida);
351	ida_destroy(&nct6694->gpio_ida);
352	irq_domain_remove(nct6694->domain);
353err_urb:
354	usb_free_urb(nct6694->int_in_urb);
355	return ret;
356}
357
358static void nct6694_usb_disconnect(struct usb_interface *iface)
359{
360	struct nct6694 *nct6694 = usb_get_intfdata(iface);
361
362	mfd_remove_devices(nct6694->dev);
363	usb_kill_urb(nct6694->int_in_urb);
364	ida_destroy(&nct6694->wdt_ida);
365	ida_destroy(&nct6694->canfd_ida);
366	ida_destroy(&nct6694->i2c_ida);
367	ida_destroy(&nct6694->gpio_ida);
368	irq_domain_remove(nct6694->domain);
369	usb_free_urb(nct6694->int_in_urb);
370}
371
372static const struct usb_device_id nct6694_ids[] = {
373	{ USB_DEVICE_AND_INTERFACE_INFO(NCT6694_VENDOR_ID, NCT6694_PRODUCT_ID, 0xFF, 0x00, 0x00) },
374	{ }
375};
376MODULE_DEVICE_TABLE(usb, nct6694_ids);
377
378static struct usb_driver nct6694_usb_driver = {
379	.name		= "nct6694",
380	.id_table	= nct6694_ids,
381	.probe		= nct6694_usb_probe,
382	.disconnect	= nct6694_usb_disconnect,
383};
384module_usb_driver(nct6694_usb_driver);
385
386MODULE_DESCRIPTION("Nuvoton NCT6694 core driver");
387MODULE_AUTHOR("Ming Yu <tmyu0@nuvoton.com>");
388MODULE_LICENSE("GPL");
~/ Read-only file preview.