mirror of https://github.com/torvalds/linux.git
mfd: Add core driver for Nuvoton NCT6694
The Nuvoton NCT6694 provides an USB interface to the host to access its features. Sub-devices can use the USB functions nct6694_read_msg() and nct6694_write_msg() to issue a command. They can also request interrupt that will be called when the USB device receives its interrupt pipe. Signed-off-by: Ming Yu <a0282524688@gmail.com> Link: https://lore.kernel.org/r/20250912091952.1169369-2-a0282524688@gmail.com Signed-off-by: Lee Jones <lee@kernel.org>
This commit is contained in:
parent
8f5ae30d69
commit
51dad33ede
|
|
@ -18082,6 +18082,12 @@ F: drivers/nubus/
|
||||||
F: include/linux/nubus.h
|
F: include/linux/nubus.h
|
||||||
F: include/uapi/linux/nubus.h
|
F: include/uapi/linux/nubus.h
|
||||||
|
|
||||||
|
NUVOTON NCT6694 MFD DRIVER
|
||||||
|
M: Ming Yu <tmyu0@nuvoton.com>
|
||||||
|
S: Supported
|
||||||
|
F: drivers/mfd/nct6694.c
|
||||||
|
F: include/linux/mfd/nct6694.h
|
||||||
|
|
||||||
NUVOTON NCT7201 IIO DRIVER
|
NUVOTON NCT7201 IIO DRIVER
|
||||||
M: Eason Yang <j2anfernee@gmail.com>
|
M: Eason Yang <j2anfernee@gmail.com>
|
||||||
L: linux-iio@vger.kernel.org
|
L: linux-iio@vger.kernel.org
|
||||||
|
|
|
||||||
|
|
@ -1134,6 +1134,21 @@ config MFD_MENF21BMC
|
||||||
This driver can also be built as a module. If so the module
|
This driver can also be built as a module. If so the module
|
||||||
will be called menf21bmc.
|
will be called menf21bmc.
|
||||||
|
|
||||||
|
config MFD_NCT6694
|
||||||
|
tristate "Nuvoton NCT6694 support"
|
||||||
|
select MFD_CORE
|
||||||
|
depends on USB
|
||||||
|
help
|
||||||
|
This enables support for the Nuvoton USB device NCT6694, which shares
|
||||||
|
peripherals.
|
||||||
|
The Nuvoton NCT6694 is a peripheral expander with 16 GPIO chips,
|
||||||
|
6 I2C controllers, 2 CANfd controllers, 2 Watchdog timers, ADC,
|
||||||
|
PWM, and RTC.
|
||||||
|
This driver provides core APIs to access the NCT6694 hardware
|
||||||
|
monitoring and control features.
|
||||||
|
Additional drivers must be enabled to utilize the specific
|
||||||
|
functionalities of the device.
|
||||||
|
|
||||||
config MFD_OCELOT
|
config MFD_OCELOT
|
||||||
tristate "Microsemi Ocelot External Control Support"
|
tristate "Microsemi Ocelot External Control Support"
|
||||||
depends on SPI_MASTER
|
depends on SPI_MASTER
|
||||||
|
|
|
||||||
|
|
@ -121,6 +121,8 @@ obj-$(CONFIG_MFD_MC13XXX) += mc13xxx-core.o
|
||||||
obj-$(CONFIG_MFD_MC13XXX_SPI) += mc13xxx-spi.o
|
obj-$(CONFIG_MFD_MC13XXX_SPI) += mc13xxx-spi.o
|
||||||
obj-$(CONFIG_MFD_MC13XXX_I2C) += mc13xxx-i2c.o
|
obj-$(CONFIG_MFD_MC13XXX_I2C) += mc13xxx-i2c.o
|
||||||
|
|
||||||
|
obj-$(CONFIG_MFD_NCT6694) += nct6694.o
|
||||||
|
|
||||||
obj-$(CONFIG_MFD_CORE) += mfd-core.o
|
obj-$(CONFIG_MFD_CORE) += mfd-core.o
|
||||||
|
|
||||||
ocelot-soc-objs := ocelot-core.o ocelot-spi.o
|
ocelot-soc-objs := ocelot-core.o ocelot-spi.o
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,388 @@
|
||||||
|
// SPDX-License-Identifier: GPL-2.0
|
||||||
|
/*
|
||||||
|
* Copyright (C) 2025 Nuvoton Technology Corp.
|
||||||
|
*
|
||||||
|
* Nuvoton NCT6694 core driver using USB interface to provide
|
||||||
|
* access to the NCT6694 hardware monitoring and control features.
|
||||||
|
*
|
||||||
|
* The NCT6694 is an integrated controller that provides GPIO, I2C,
|
||||||
|
* CAN, WDT, HWMON and RTC management.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <linux/bits.h>
|
||||||
|
#include <linux/interrupt.h>
|
||||||
|
#include <linux/idr.h>
|
||||||
|
#include <linux/irq.h>
|
||||||
|
#include <linux/irqdomain.h>
|
||||||
|
#include <linux/kernel.h>
|
||||||
|
#include <linux/mfd/core.h>
|
||||||
|
#include <linux/mfd/nct6694.h>
|
||||||
|
#include <linux/module.h>
|
||||||
|
#include <linux/slab.h>
|
||||||
|
#include <linux/spinlock.h>
|
||||||
|
#include <linux/usb.h>
|
||||||
|
|
||||||
|
static const struct mfd_cell nct6694_devs[] = {
|
||||||
|
MFD_CELL_NAME("nct6694-gpio"),
|
||||||
|
MFD_CELL_NAME("nct6694-gpio"),
|
||||||
|
MFD_CELL_NAME("nct6694-gpio"),
|
||||||
|
MFD_CELL_NAME("nct6694-gpio"),
|
||||||
|
MFD_CELL_NAME("nct6694-gpio"),
|
||||||
|
MFD_CELL_NAME("nct6694-gpio"),
|
||||||
|
MFD_CELL_NAME("nct6694-gpio"),
|
||||||
|
MFD_CELL_NAME("nct6694-gpio"),
|
||||||
|
MFD_CELL_NAME("nct6694-gpio"),
|
||||||
|
MFD_CELL_NAME("nct6694-gpio"),
|
||||||
|
MFD_CELL_NAME("nct6694-gpio"),
|
||||||
|
MFD_CELL_NAME("nct6694-gpio"),
|
||||||
|
MFD_CELL_NAME("nct6694-gpio"),
|
||||||
|
MFD_CELL_NAME("nct6694-gpio"),
|
||||||
|
MFD_CELL_NAME("nct6694-gpio"),
|
||||||
|
MFD_CELL_NAME("nct6694-gpio"),
|
||||||
|
|
||||||
|
MFD_CELL_NAME("nct6694-i2c"),
|
||||||
|
MFD_CELL_NAME("nct6694-i2c"),
|
||||||
|
MFD_CELL_NAME("nct6694-i2c"),
|
||||||
|
MFD_CELL_NAME("nct6694-i2c"),
|
||||||
|
MFD_CELL_NAME("nct6694-i2c"),
|
||||||
|
MFD_CELL_NAME("nct6694-i2c"),
|
||||||
|
|
||||||
|
MFD_CELL_NAME("nct6694-canfd"),
|
||||||
|
MFD_CELL_NAME("nct6694-canfd"),
|
||||||
|
|
||||||
|
MFD_CELL_NAME("nct6694-wdt"),
|
||||||
|
MFD_CELL_NAME("nct6694-wdt"),
|
||||||
|
|
||||||
|
MFD_CELL_NAME("nct6694-hwmon"),
|
||||||
|
|
||||||
|
MFD_CELL_NAME("nct6694-rtc"),
|
||||||
|
};
|
||||||
|
|
||||||
|
static int nct6694_response_err_handling(struct nct6694 *nct6694, unsigned char err_status)
|
||||||
|
{
|
||||||
|
switch (err_status) {
|
||||||
|
case NCT6694_NO_ERROR:
|
||||||
|
return 0;
|
||||||
|
case NCT6694_NOT_SUPPORT_ERROR:
|
||||||
|
dev_err(nct6694->dev, "Command is not supported!\n");
|
||||||
|
break;
|
||||||
|
case NCT6694_NO_RESPONSE_ERROR:
|
||||||
|
dev_warn(nct6694->dev, "Command received no response!\n");
|
||||||
|
break;
|
||||||
|
case NCT6694_TIMEOUT_ERROR:
|
||||||
|
dev_warn(nct6694->dev, "Command timed out!\n");
|
||||||
|
break;
|
||||||
|
case NCT6694_PENDING:
|
||||||
|
dev_err(nct6694->dev, "Command is pending!\n");
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
return -EINVAL;
|
||||||
|
}
|
||||||
|
|
||||||
|
return -EIO;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* nct6694_read_msg() - Read message from NCT6694 device
|
||||||
|
* @nct6694: NCT6694 device pointer
|
||||||
|
* @cmd_hd: command header structure
|
||||||
|
* @buf: buffer to store the response data
|
||||||
|
*
|
||||||
|
* Sends a command to the NCT6694 device and reads the response.
|
||||||
|
* The command header is specified in @cmd_hd, and the response
|
||||||
|
* data is stored in @buf.
|
||||||
|
*
|
||||||
|
* Return: Negative value on error or 0 on success.
|
||||||
|
*/
|
||||||
|
int nct6694_read_msg(struct nct6694 *nct6694, const struct nct6694_cmd_header *cmd_hd, void *buf)
|
||||||
|
{
|
||||||
|
union nct6694_usb_msg *msg = nct6694->usb_msg;
|
||||||
|
struct usb_device *udev = nct6694->udev;
|
||||||
|
int tx_len, rx_len, ret;
|
||||||
|
|
||||||
|
guard(mutex)(&nct6694->access_lock);
|
||||||
|
|
||||||
|
memcpy(&msg->cmd_header, cmd_hd, sizeof(*cmd_hd));
|
||||||
|
msg->cmd_header.hctrl = NCT6694_HCTRL_GET;
|
||||||
|
|
||||||
|
/* Send command packet to USB device */
|
||||||
|
ret = usb_bulk_msg(udev, usb_sndbulkpipe(udev, NCT6694_BULK_OUT_EP), &msg->cmd_header,
|
||||||
|
sizeof(*msg), &tx_len, NCT6694_URB_TIMEOUT);
|
||||||
|
if (ret)
|
||||||
|
return ret;
|
||||||
|
|
||||||
|
/* Receive response packet from USB device */
|
||||||
|
ret = usb_bulk_msg(udev, usb_rcvbulkpipe(udev, NCT6694_BULK_IN_EP), &msg->response_header,
|
||||||
|
sizeof(*msg), &rx_len, NCT6694_URB_TIMEOUT);
|
||||||
|
if (ret)
|
||||||
|
return ret;
|
||||||
|
|
||||||
|
/* Receive data packet from USB device */
|
||||||
|
ret = usb_bulk_msg(udev, usb_rcvbulkpipe(udev, NCT6694_BULK_IN_EP), buf,
|
||||||
|
le16_to_cpu(cmd_hd->len), &rx_len, NCT6694_URB_TIMEOUT);
|
||||||
|
if (ret)
|
||||||
|
return ret;
|
||||||
|
|
||||||
|
if (rx_len != le16_to_cpu(cmd_hd->len)) {
|
||||||
|
dev_err(nct6694->dev, "Expected received length %d, but got %d\n",
|
||||||
|
le16_to_cpu(cmd_hd->len), rx_len);
|
||||||
|
return -EIO;
|
||||||
|
}
|
||||||
|
|
||||||
|
return nct6694_response_err_handling(nct6694, msg->response_header.sts);
|
||||||
|
}
|
||||||
|
EXPORT_SYMBOL_GPL(nct6694_read_msg);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* nct6694_write_msg() - Write message to NCT6694 device
|
||||||
|
* @nct6694: NCT6694 device pointer
|
||||||
|
* @cmd_hd: command header structure
|
||||||
|
* @buf: buffer containing the data to be sent
|
||||||
|
*
|
||||||
|
* Sends a command to the NCT6694 device and writes the data
|
||||||
|
* from @buf. The command header is specified in @cmd_hd.
|
||||||
|
*
|
||||||
|
* Return: Negative value on error or 0 on success.
|
||||||
|
*/
|
||||||
|
int nct6694_write_msg(struct nct6694 *nct6694, const struct nct6694_cmd_header *cmd_hd, void *buf)
|
||||||
|
{
|
||||||
|
union nct6694_usb_msg *msg = nct6694->usb_msg;
|
||||||
|
struct usb_device *udev = nct6694->udev;
|
||||||
|
int tx_len, rx_len, ret;
|
||||||
|
|
||||||
|
guard(mutex)(&nct6694->access_lock);
|
||||||
|
|
||||||
|
memcpy(&msg->cmd_header, cmd_hd, sizeof(*cmd_hd));
|
||||||
|
msg->cmd_header.hctrl = NCT6694_HCTRL_SET;
|
||||||
|
|
||||||
|
/* Send command packet to USB device */
|
||||||
|
ret = usb_bulk_msg(udev, usb_sndbulkpipe(udev, NCT6694_BULK_OUT_EP), &msg->cmd_header,
|
||||||
|
sizeof(*msg), &tx_len, NCT6694_URB_TIMEOUT);
|
||||||
|
if (ret)
|
||||||
|
return ret;
|
||||||
|
|
||||||
|
/* Send data packet to USB device */
|
||||||
|
ret = usb_bulk_msg(udev, usb_sndbulkpipe(udev, NCT6694_BULK_OUT_EP), buf,
|
||||||
|
le16_to_cpu(cmd_hd->len), &tx_len, NCT6694_URB_TIMEOUT);
|
||||||
|
if (ret)
|
||||||
|
return ret;
|
||||||
|
|
||||||
|
/* Receive response packet from USB device */
|
||||||
|
ret = usb_bulk_msg(udev, usb_rcvbulkpipe(udev, NCT6694_BULK_IN_EP), &msg->response_header,
|
||||||
|
sizeof(*msg), &rx_len, NCT6694_URB_TIMEOUT);
|
||||||
|
if (ret)
|
||||||
|
return ret;
|
||||||
|
|
||||||
|
/* Receive data packet from USB device */
|
||||||
|
ret = usb_bulk_msg(udev, usb_rcvbulkpipe(udev, NCT6694_BULK_IN_EP), buf,
|
||||||
|
le16_to_cpu(cmd_hd->len), &rx_len, NCT6694_URB_TIMEOUT);
|
||||||
|
if (ret)
|
||||||
|
return ret;
|
||||||
|
|
||||||
|
if (rx_len != le16_to_cpu(cmd_hd->len)) {
|
||||||
|
dev_err(nct6694->dev, "Expected transmitted length %d, but got %d\n",
|
||||||
|
le16_to_cpu(cmd_hd->len), rx_len);
|
||||||
|
return -EIO;
|
||||||
|
}
|
||||||
|
|
||||||
|
return nct6694_response_err_handling(nct6694, msg->response_header.sts);
|
||||||
|
}
|
||||||
|
EXPORT_SYMBOL_GPL(nct6694_write_msg);
|
||||||
|
|
||||||
|
static void usb_int_callback(struct urb *urb)
|
||||||
|
{
|
||||||
|
struct nct6694 *nct6694 = urb->context;
|
||||||
|
__le32 *status_le = urb->transfer_buffer;
|
||||||
|
u32 int_status;
|
||||||
|
int ret;
|
||||||
|
|
||||||
|
switch (urb->status) {
|
||||||
|
case 0:
|
||||||
|
break;
|
||||||
|
case -ECONNRESET:
|
||||||
|
case -ENOENT:
|
||||||
|
case -ESHUTDOWN:
|
||||||
|
return;
|
||||||
|
default:
|
||||||
|
goto resubmit;
|
||||||
|
}
|
||||||
|
|
||||||
|
int_status = le32_to_cpu(*status_le);
|
||||||
|
|
||||||
|
while (int_status) {
|
||||||
|
int irq = __ffs(int_status);
|
||||||
|
|
||||||
|
generic_handle_irq_safe(irq_find_mapping(nct6694->domain, irq));
|
||||||
|
int_status &= ~BIT(irq);
|
||||||
|
}
|
||||||
|
|
||||||
|
resubmit:
|
||||||
|
ret = usb_submit_urb(urb, GFP_ATOMIC);
|
||||||
|
if (ret)
|
||||||
|
dev_warn(nct6694->dev, "Failed to resubmit urb, status %pe", ERR_PTR(ret));
|
||||||
|
}
|
||||||
|
|
||||||
|
static void nct6694_irq_enable(struct irq_data *data)
|
||||||
|
{
|
||||||
|
struct nct6694 *nct6694 = irq_data_get_irq_chip_data(data);
|
||||||
|
irq_hw_number_t hwirq = irqd_to_hwirq(data);
|
||||||
|
|
||||||
|
guard(spinlock_irqsave)(&nct6694->irq_lock);
|
||||||
|
|
||||||
|
nct6694->irq_enable |= BIT(hwirq);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void nct6694_irq_disable(struct irq_data *data)
|
||||||
|
{
|
||||||
|
struct nct6694 *nct6694 = irq_data_get_irq_chip_data(data);
|
||||||
|
irq_hw_number_t hwirq = irqd_to_hwirq(data);
|
||||||
|
|
||||||
|
guard(spinlock_irqsave)(&nct6694->irq_lock);
|
||||||
|
|
||||||
|
nct6694->irq_enable &= ~BIT(hwirq);
|
||||||
|
}
|
||||||
|
|
||||||
|
static const struct irq_chip nct6694_irq_chip = {
|
||||||
|
.name = "nct6694-irq",
|
||||||
|
.flags = IRQCHIP_SKIP_SET_WAKE,
|
||||||
|
.irq_enable = nct6694_irq_enable,
|
||||||
|
.irq_disable = nct6694_irq_disable,
|
||||||
|
};
|
||||||
|
|
||||||
|
static int nct6694_irq_domain_map(struct irq_domain *d, unsigned int irq, irq_hw_number_t hw)
|
||||||
|
{
|
||||||
|
struct nct6694 *nct6694 = d->host_data;
|
||||||
|
|
||||||
|
irq_set_chip_data(irq, nct6694);
|
||||||
|
irq_set_chip_and_handler(irq, &nct6694_irq_chip, handle_simple_irq);
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void nct6694_irq_domain_unmap(struct irq_domain *d, unsigned int irq)
|
||||||
|
{
|
||||||
|
irq_set_chip_and_handler(irq, NULL, NULL);
|
||||||
|
irq_set_chip_data(irq, NULL);
|
||||||
|
}
|
||||||
|
|
||||||
|
static const struct irq_domain_ops nct6694_irq_domain_ops = {
|
||||||
|
.map = nct6694_irq_domain_map,
|
||||||
|
.unmap = nct6694_irq_domain_unmap,
|
||||||
|
};
|
||||||
|
|
||||||
|
static int nct6694_usb_probe(struct usb_interface *iface,
|
||||||
|
const struct usb_device_id *id)
|
||||||
|
{
|
||||||
|
struct usb_device *udev = interface_to_usbdev(iface);
|
||||||
|
struct usb_endpoint_descriptor *int_endpoint;
|
||||||
|
struct usb_host_interface *interface;
|
||||||
|
struct device *dev = &iface->dev;
|
||||||
|
struct nct6694 *nct6694;
|
||||||
|
int ret;
|
||||||
|
|
||||||
|
nct6694 = devm_kzalloc(dev, sizeof(*nct6694), GFP_KERNEL);
|
||||||
|
if (!nct6694)
|
||||||
|
return -ENOMEM;
|
||||||
|
|
||||||
|
nct6694->usb_msg = devm_kzalloc(dev, sizeof(union nct6694_usb_msg), GFP_KERNEL);
|
||||||
|
if (!nct6694->usb_msg)
|
||||||
|
return -ENOMEM;
|
||||||
|
|
||||||
|
nct6694->int_buffer = devm_kzalloc(dev, sizeof(*nct6694->int_buffer), GFP_KERNEL);
|
||||||
|
if (!nct6694->int_buffer)
|
||||||
|
return -ENOMEM;
|
||||||
|
|
||||||
|
nct6694->int_in_urb = usb_alloc_urb(0, GFP_KERNEL);
|
||||||
|
if (!nct6694->int_in_urb)
|
||||||
|
return -ENOMEM;
|
||||||
|
|
||||||
|
nct6694->domain = irq_domain_create_simple(NULL, NCT6694_NR_IRQS, 0,
|
||||||
|
&nct6694_irq_domain_ops,
|
||||||
|
nct6694);
|
||||||
|
if (!nct6694->domain) {
|
||||||
|
ret = -ENODEV;
|
||||||
|
goto err_urb;
|
||||||
|
}
|
||||||
|
|
||||||
|
nct6694->dev = dev;
|
||||||
|
nct6694->udev = udev;
|
||||||
|
|
||||||
|
ida_init(&nct6694->gpio_ida);
|
||||||
|
ida_init(&nct6694->i2c_ida);
|
||||||
|
ida_init(&nct6694->canfd_ida);
|
||||||
|
ida_init(&nct6694->wdt_ida);
|
||||||
|
|
||||||
|
spin_lock_init(&nct6694->irq_lock);
|
||||||
|
|
||||||
|
ret = devm_mutex_init(dev, &nct6694->access_lock);
|
||||||
|
if (ret)
|
||||||
|
goto err_ida;
|
||||||
|
|
||||||
|
interface = iface->cur_altsetting;
|
||||||
|
|
||||||
|
int_endpoint = &interface->endpoint[0].desc;
|
||||||
|
if (!usb_endpoint_is_int_in(int_endpoint)) {
|
||||||
|
ret = -ENODEV;
|
||||||
|
goto err_ida;
|
||||||
|
}
|
||||||
|
|
||||||
|
usb_fill_int_urb(nct6694->int_in_urb, udev, usb_rcvintpipe(udev, NCT6694_INT_IN_EP),
|
||||||
|
nct6694->int_buffer, sizeof(*nct6694->int_buffer), usb_int_callback,
|
||||||
|
nct6694, int_endpoint->bInterval);
|
||||||
|
|
||||||
|
ret = usb_submit_urb(nct6694->int_in_urb, GFP_KERNEL);
|
||||||
|
if (ret)
|
||||||
|
goto err_ida;
|
||||||
|
|
||||||
|
usb_set_intfdata(iface, nct6694);
|
||||||
|
|
||||||
|
ret = mfd_add_hotplug_devices(dev, nct6694_devs, ARRAY_SIZE(nct6694_devs));
|
||||||
|
if (ret)
|
||||||
|
goto err_mfd;
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
err_mfd:
|
||||||
|
usb_kill_urb(nct6694->int_in_urb);
|
||||||
|
err_ida:
|
||||||
|
ida_destroy(&nct6694->wdt_ida);
|
||||||
|
ida_destroy(&nct6694->canfd_ida);
|
||||||
|
ida_destroy(&nct6694->i2c_ida);
|
||||||
|
ida_destroy(&nct6694->gpio_ida);
|
||||||
|
irq_domain_remove(nct6694->domain);
|
||||||
|
err_urb:
|
||||||
|
usb_free_urb(nct6694->int_in_urb);
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void nct6694_usb_disconnect(struct usb_interface *iface)
|
||||||
|
{
|
||||||
|
struct nct6694 *nct6694 = usb_get_intfdata(iface);
|
||||||
|
|
||||||
|
mfd_remove_devices(nct6694->dev);
|
||||||
|
usb_kill_urb(nct6694->int_in_urb);
|
||||||
|
ida_destroy(&nct6694->wdt_ida);
|
||||||
|
ida_destroy(&nct6694->canfd_ida);
|
||||||
|
ida_destroy(&nct6694->i2c_ida);
|
||||||
|
ida_destroy(&nct6694->gpio_ida);
|
||||||
|
irq_domain_remove(nct6694->domain);
|
||||||
|
usb_free_urb(nct6694->int_in_urb);
|
||||||
|
}
|
||||||
|
|
||||||
|
static const struct usb_device_id nct6694_ids[] = {
|
||||||
|
{ USB_DEVICE_AND_INTERFACE_INFO(NCT6694_VENDOR_ID, NCT6694_PRODUCT_ID, 0xFF, 0x00, 0x00) },
|
||||||
|
{ }
|
||||||
|
};
|
||||||
|
MODULE_DEVICE_TABLE(usb, nct6694_ids);
|
||||||
|
|
||||||
|
static struct usb_driver nct6694_usb_driver = {
|
||||||
|
.name = "nct6694",
|
||||||
|
.id_table = nct6694_ids,
|
||||||
|
.probe = nct6694_usb_probe,
|
||||||
|
.disconnect = nct6694_usb_disconnect,
|
||||||
|
};
|
||||||
|
module_usb_driver(nct6694_usb_driver);
|
||||||
|
|
||||||
|
MODULE_DESCRIPTION("Nuvoton NCT6694 core driver");
|
||||||
|
MODULE_AUTHOR("Ming Yu <tmyu0@nuvoton.com>");
|
||||||
|
MODULE_LICENSE("GPL");
|
||||||
|
|
@ -0,0 +1,102 @@
|
||||||
|
/* SPDX-License-Identifier: GPL-2.0 */
|
||||||
|
/*
|
||||||
|
* Copyright (C) 2025 Nuvoton Technology Corp.
|
||||||
|
*
|
||||||
|
* Nuvoton NCT6694 USB transaction and data structure.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef __MFD_NCT6694_H
|
||||||
|
#define __MFD_NCT6694_H
|
||||||
|
|
||||||
|
#define NCT6694_VENDOR_ID 0x0416
|
||||||
|
#define NCT6694_PRODUCT_ID 0x200B
|
||||||
|
#define NCT6694_INT_IN_EP 0x81
|
||||||
|
#define NCT6694_BULK_IN_EP 0x02
|
||||||
|
#define NCT6694_BULK_OUT_EP 0x03
|
||||||
|
|
||||||
|
#define NCT6694_HCTRL_SET 0x40
|
||||||
|
#define NCT6694_HCTRL_GET 0x80
|
||||||
|
|
||||||
|
#define NCT6694_URB_TIMEOUT 1000
|
||||||
|
|
||||||
|
enum nct6694_irq_id {
|
||||||
|
NCT6694_IRQ_GPIO0 = 0,
|
||||||
|
NCT6694_IRQ_GPIO1,
|
||||||
|
NCT6694_IRQ_GPIO2,
|
||||||
|
NCT6694_IRQ_GPIO3,
|
||||||
|
NCT6694_IRQ_GPIO4,
|
||||||
|
NCT6694_IRQ_GPIO5,
|
||||||
|
NCT6694_IRQ_GPIO6,
|
||||||
|
NCT6694_IRQ_GPIO7,
|
||||||
|
NCT6694_IRQ_GPIO8,
|
||||||
|
NCT6694_IRQ_GPIO9,
|
||||||
|
NCT6694_IRQ_GPIOA,
|
||||||
|
NCT6694_IRQ_GPIOB,
|
||||||
|
NCT6694_IRQ_GPIOC,
|
||||||
|
NCT6694_IRQ_GPIOD,
|
||||||
|
NCT6694_IRQ_GPIOE,
|
||||||
|
NCT6694_IRQ_GPIOF,
|
||||||
|
NCT6694_IRQ_CAN0,
|
||||||
|
NCT6694_IRQ_CAN1,
|
||||||
|
NCT6694_IRQ_RTC,
|
||||||
|
NCT6694_NR_IRQS,
|
||||||
|
};
|
||||||
|
|
||||||
|
enum nct6694_response_err_status {
|
||||||
|
NCT6694_NO_ERROR = 0,
|
||||||
|
NCT6694_FORMAT_ERROR,
|
||||||
|
NCT6694_RESERVED1,
|
||||||
|
NCT6694_RESERVED2,
|
||||||
|
NCT6694_NOT_SUPPORT_ERROR,
|
||||||
|
NCT6694_NO_RESPONSE_ERROR,
|
||||||
|
NCT6694_TIMEOUT_ERROR,
|
||||||
|
NCT6694_PENDING,
|
||||||
|
};
|
||||||
|
|
||||||
|
struct __packed nct6694_cmd_header {
|
||||||
|
u8 rsv1;
|
||||||
|
u8 mod;
|
||||||
|
union __packed {
|
||||||
|
__le16 offset;
|
||||||
|
struct __packed {
|
||||||
|
u8 cmd;
|
||||||
|
u8 sel;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
u8 hctrl;
|
||||||
|
u8 rsv2;
|
||||||
|
__le16 len;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct __packed nct6694_response_header {
|
||||||
|
u8 sequence_id;
|
||||||
|
u8 sts;
|
||||||
|
u8 reserved[4];
|
||||||
|
__le16 len;
|
||||||
|
};
|
||||||
|
|
||||||
|
union __packed nct6694_usb_msg {
|
||||||
|
struct nct6694_cmd_header cmd_header;
|
||||||
|
struct nct6694_response_header response_header;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct nct6694 {
|
||||||
|
struct device *dev;
|
||||||
|
struct ida gpio_ida;
|
||||||
|
struct ida i2c_ida;
|
||||||
|
struct ida canfd_ida;
|
||||||
|
struct ida wdt_ida;
|
||||||
|
struct irq_domain *domain;
|
||||||
|
struct mutex access_lock;
|
||||||
|
spinlock_t irq_lock;
|
||||||
|
struct urb *int_in_urb;
|
||||||
|
struct usb_device *udev;
|
||||||
|
union nct6694_usb_msg *usb_msg;
|
||||||
|
__le32 *int_buffer;
|
||||||
|
unsigned int irq_enable;
|
||||||
|
};
|
||||||
|
|
||||||
|
int nct6694_read_msg(struct nct6694 *nct6694, const struct nct6694_cmd_header *cmd_hd, void *buf);
|
||||||
|
int nct6694_write_msg(struct nct6694 *nct6694, const struct nct6694_cmd_header *cmd_hd, void *buf);
|
||||||
|
|
||||||
|
#endif
|
||||||
Loading…
Reference in New Issue