diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 18:49:45 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 18:49:45 +0000 |
commit | 2c3c1048746a4622d8c89a29670120dc8fab93c4 (patch) | |
tree | 848558de17fb3008cdf4d861b01ac7781903ce39 /drivers/usb/musb/jz4740.c | |
parent | Initial commit. (diff) | |
download | linux-upstream.tar.xz linux-upstream.zip |
Adding upstream version 6.1.76.upstream/6.1.76upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'drivers/usb/musb/jz4740.c')
-rw-r--r-- | drivers/usb/musb/jz4740.c | 294 |
1 files changed, 294 insertions, 0 deletions
diff --git a/drivers/usb/musb/jz4740.c b/drivers/usb/musb/jz4740.c new file mode 100644 index 000000000..d1e4e0deb --- /dev/null +++ b/drivers/usb/musb/jz4740.c @@ -0,0 +1,294 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Ingenic JZ4740 "glue layer" + * + * Copyright (C) 2013, Apelete Seketeli <apelete@seketeli.net> + */ + +#include <linux/clk.h> +#include <linux/dma-mapping.h> +#include <linux/errno.h> +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/of_device.h> +#include <linux/platform_device.h> +#include <linux/usb/role.h> +#include <linux/usb/usb_phy_generic.h> + +#include "musb_core.h" + +struct jz4740_glue { + struct platform_device *pdev; + struct musb *musb; + struct clk *clk; + struct usb_role_switch *role_sw; +}; + +static irqreturn_t jz4740_musb_interrupt(int irq, void *__hci) +{ + unsigned long flags; + irqreturn_t retval = IRQ_NONE, retval_dma = IRQ_NONE; + struct musb *musb = __hci; + + if (IS_ENABLED(CONFIG_USB_INVENTRA_DMA) && musb->dma_controller) + retval_dma = dma_controller_irq(irq, musb->dma_controller); + + spin_lock_irqsave(&musb->lock, flags); + + musb->int_usb = musb_readb(musb->mregs, MUSB_INTRUSB); + musb->int_tx = musb_readw(musb->mregs, MUSB_INTRTX); + musb->int_rx = musb_readw(musb->mregs, MUSB_INTRRX); + + /* + * The controller is gadget only, the state of the host mode IRQ bits is + * undefined. Mask them to make sure that the musb driver core will + * never see them set + */ + musb->int_usb &= MUSB_INTR_SUSPEND | MUSB_INTR_RESUME | + MUSB_INTR_RESET | MUSB_INTR_SOF; + + if (musb->int_usb || musb->int_tx || musb->int_rx) + retval = musb_interrupt(musb); + + spin_unlock_irqrestore(&musb->lock, flags); + + if (retval == IRQ_HANDLED || retval_dma == IRQ_HANDLED) + return IRQ_HANDLED; + + return IRQ_NONE; +} + +static struct musb_fifo_cfg jz4740_musb_fifo_cfg[] = { + { .hw_ep_num = 1, .style = FIFO_TX, .maxpacket = 512, }, + { .hw_ep_num = 1, .style = FIFO_RX, .maxpacket = 512, }, + { .hw_ep_num = 2, .style = FIFO_TX, .maxpacket = 64, }, +}; + +static const struct musb_hdrc_config jz4740_musb_config = { + /* Silicon does not implement USB OTG. */ + .multipoint = 0, + /* Max EPs scanned, driver will decide which EP can be used. */ + .num_eps = 4, + /* RAMbits needed to configure EPs from table */ + .ram_bits = 9, + .fifo_cfg = jz4740_musb_fifo_cfg, + .fifo_cfg_size = ARRAY_SIZE(jz4740_musb_fifo_cfg), +}; + +static int jz4740_musb_role_switch_set(struct usb_role_switch *sw, + enum usb_role role) +{ + struct jz4740_glue *glue = usb_role_switch_get_drvdata(sw); + struct usb_phy *phy = glue->musb->xceiv; + + switch (role) { + case USB_ROLE_NONE: + atomic_notifier_call_chain(&phy->notifier, USB_EVENT_NONE, phy); + break; + case USB_ROLE_DEVICE: + atomic_notifier_call_chain(&phy->notifier, USB_EVENT_VBUS, phy); + break; + case USB_ROLE_HOST: + atomic_notifier_call_chain(&phy->notifier, USB_EVENT_ID, phy); + break; + } + + return 0; +} + +static int jz4740_musb_init(struct musb *musb) +{ + struct device *dev = musb->controller->parent; + struct jz4740_glue *glue = dev_get_drvdata(dev); + struct usb_role_switch_desc role_sw_desc = { + .set = jz4740_musb_role_switch_set, + .driver_data = glue, + .fwnode = dev_fwnode(dev), + }; + + glue->musb = musb; + + if (dev->of_node) + musb->xceiv = devm_usb_get_phy_by_phandle(dev, "phys", 0); + else + musb->xceiv = devm_usb_get_phy(dev, USB_PHY_TYPE_USB2); + if (IS_ERR(musb->xceiv)) + return dev_err_probe(dev, PTR_ERR(musb->xceiv), + "No transceiver configured\n"); + + glue->role_sw = usb_role_switch_register(dev, &role_sw_desc); + if (IS_ERR(glue->role_sw)) { + dev_err(dev, "Failed to register USB role switch\n"); + return PTR_ERR(glue->role_sw); + } + + /* + * Silicon does not implement ConfigData register. + * Set dyn_fifo to avoid reading EP config from hardware. + */ + musb->dyn_fifo = true; + + musb->isr = jz4740_musb_interrupt; + + return 0; +} + +static int jz4740_musb_exit(struct musb *musb) +{ + struct jz4740_glue *glue = dev_get_drvdata(musb->controller->parent); + + usb_role_switch_unregister(glue->role_sw); + + return 0; +} + +static const struct musb_platform_ops jz4740_musb_ops = { + .quirks = MUSB_DMA_INVENTRA | MUSB_INDEXED_EP, + .fifo_mode = 2, + .init = jz4740_musb_init, + .exit = jz4740_musb_exit, +#ifdef CONFIG_USB_INVENTRA_DMA + .dma_init = musbhs_dma_controller_create_noirq, + .dma_exit = musbhs_dma_controller_destroy, +#endif +}; + +static const struct musb_hdrc_platform_data jz4740_musb_pdata = { + .mode = MUSB_PERIPHERAL, + .config = &jz4740_musb_config, + .platform_ops = &jz4740_musb_ops, +}; + +static struct musb_fifo_cfg jz4770_musb_fifo_cfg[] = { + { .hw_ep_num = 1, .style = FIFO_TX, .maxpacket = 512, }, + { .hw_ep_num = 1, .style = FIFO_RX, .maxpacket = 512, }, + { .hw_ep_num = 2, .style = FIFO_TX, .maxpacket = 512, }, + { .hw_ep_num = 2, .style = FIFO_RX, .maxpacket = 512, }, + { .hw_ep_num = 3, .style = FIFO_TX, .maxpacket = 512, }, + { .hw_ep_num = 3, .style = FIFO_RX, .maxpacket = 512, }, + { .hw_ep_num = 4, .style = FIFO_TX, .maxpacket = 512, }, + { .hw_ep_num = 4, .style = FIFO_RX, .maxpacket = 512, }, + { .hw_ep_num = 5, .style = FIFO_TX, .maxpacket = 512, }, + { .hw_ep_num = 5, .style = FIFO_RX, .maxpacket = 512, }, +}; + +static struct musb_hdrc_config jz4770_musb_config = { + .multipoint = 1, + .num_eps = 11, + .ram_bits = 11, + .fifo_cfg = jz4770_musb_fifo_cfg, + .fifo_cfg_size = ARRAY_SIZE(jz4770_musb_fifo_cfg), +}; + +static const struct musb_hdrc_platform_data jz4770_musb_pdata = { + .mode = MUSB_PERIPHERAL, /* TODO: support OTG */ + .config = &jz4770_musb_config, + .platform_ops = &jz4740_musb_ops, +}; + +static int jz4740_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + const struct musb_hdrc_platform_data *pdata; + struct platform_device *musb; + struct jz4740_glue *glue; + struct clk *clk; + int ret; + + glue = devm_kzalloc(dev, sizeof(*glue), GFP_KERNEL); + if (!glue) + return -ENOMEM; + + pdata = of_device_get_match_data(dev); + if (!pdata) { + dev_err(dev, "missing platform data\n"); + return -EINVAL; + } + + musb = platform_device_alloc("musb-hdrc", PLATFORM_DEVID_AUTO); + if (!musb) { + dev_err(dev, "failed to allocate musb device\n"); + return -ENOMEM; + } + + clk = devm_clk_get(dev, "udc"); + if (IS_ERR(clk)) { + dev_err(dev, "failed to get clock\n"); + ret = PTR_ERR(clk); + goto err_platform_device_put; + } + + ret = clk_prepare_enable(clk); + if (ret) { + dev_err(dev, "failed to enable clock\n"); + goto err_platform_device_put; + } + + musb->dev.parent = dev; + musb->dev.dma_mask = &musb->dev.coherent_dma_mask; + musb->dev.coherent_dma_mask = DMA_BIT_MASK(32); + device_set_of_node_from_dev(&musb->dev, dev); + + glue->pdev = musb; + glue->clk = clk; + + platform_set_drvdata(pdev, glue); + + ret = platform_device_add_resources(musb, pdev->resource, + pdev->num_resources); + if (ret) { + dev_err(dev, "failed to add resources\n"); + goto err_clk_disable; + } + + ret = platform_device_add_data(musb, pdata, sizeof(*pdata)); + if (ret) { + dev_err(dev, "failed to add platform_data\n"); + goto err_clk_disable; + } + + ret = platform_device_add(musb); + if (ret) { + dev_err(dev, "failed to register musb device\n"); + goto err_clk_disable; + } + + return 0; + +err_clk_disable: + clk_disable_unprepare(clk); +err_platform_device_put: + platform_device_put(musb); + return ret; +} + +static int jz4740_remove(struct platform_device *pdev) +{ + struct jz4740_glue *glue = platform_get_drvdata(pdev); + + platform_device_unregister(glue->pdev); + clk_disable_unprepare(glue->clk); + + return 0; +} + +static const struct of_device_id jz4740_musb_of_match[] = { + { .compatible = "ingenic,jz4740-musb", .data = &jz4740_musb_pdata }, + { .compatible = "ingenic,jz4770-musb", .data = &jz4770_musb_pdata }, + { /* sentinel */ }, +}; +MODULE_DEVICE_TABLE(of, jz4740_musb_of_match); + +static struct platform_driver jz4740_driver = { + .probe = jz4740_probe, + .remove = jz4740_remove, + .driver = { + .name = "musb-jz4740", + .of_match_table = jz4740_musb_of_match, + }, +}; + +MODULE_DESCRIPTION("JZ4740 MUSB Glue Layer"); +MODULE_AUTHOR("Apelete Seketeli <apelete@seketeli.net>"); +MODULE_LICENSE("GPL v2"); +module_platform_driver(jz4740_driver); |