Skip to content

Commit

Permalink
usb: musb: Add support for PolarFire SoC's musb controller
Browse files Browse the repository at this point in the history
Add support for Microchips's PolarFire SoC's musb controller in host,
peripheral and otg mode.

Tested-by: Valentina Fernandez <[email protected]>
Signed-off-by: Conor Dooley <[email protected]>
Link: https://lore.kernel.org/r/[email protected]
Signed-off-by: Greg Kroah-Hartman <[email protected]>
  • Loading branch information
ConchuOD authored and gregkh committed Jun 21, 2022
1 parent 44830e1 commit 7a96b6e
Show file tree
Hide file tree
Showing 3 changed files with 278 additions and 1 deletion.
13 changes: 12 additions & 1 deletion drivers/usb/musb/Kconfig
Original file line number Diff line number Diff line change
Expand Up @@ -123,6 +123,17 @@ config USB_MUSB_MEDIATEK
select GENERIC_PHY
select USB_ROLE_SWITCH

config USB_MUSB_POLARFIRE_SOC
tristate "Microchip PolarFire SoC platforms"
depends on SOC_MICROCHIP_POLARFIRE || COMPILE_TEST
depends on NOP_USB_XCEIV
select USB_MUSB_DUAL_ROLE
help
Say Y here to enable support for USB on Microchip's PolarFire SoC.

This support is also available as a module. If so, the module
will be called mpfs.

comment "MUSB DMA mode"

config MUSB_PIO_ONLY
Expand All @@ -146,7 +157,7 @@ config USB_UX500_DMA

config USB_INVENTRA_DMA
bool 'Inventra'
depends on USB_MUSB_OMAP2PLUS || USB_MUSB_MEDIATEK || USB_MUSB_JZ4740
depends on USB_MUSB_OMAP2PLUS || USB_MUSB_MEDIATEK || USB_MUSB_JZ4740 || USB_MUSB_POLARFIRE_SOC
help
Enable DMA transfers using Mentor's engine.

Expand Down
1 change: 1 addition & 0 deletions drivers/usb/musb/Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ obj-$(CONFIG_USB_MUSB_UX500) += ux500.o
obj-$(CONFIG_USB_MUSB_JZ4740) += jz4740.o
obj-$(CONFIG_USB_MUSB_SUNXI) += sunxi.o
obj-$(CONFIG_USB_MUSB_MEDIATEK) += mediatek.o
obj-$(CONFIG_USB_MUSB_POLARFIRE_SOC) += mpfs.o

# the kconfig must guarantee that only one of the
# possible I/O schemes will be enabled at a time ...
Expand Down
265 changes: 265 additions & 0 deletions drivers/usb/musb/mpfs.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,265 @@
// SPDX-License-Identifier: GPL-2.0
/*
* PolarFire SoC (MPFS) MUSB Glue Layer
*
* Copyright (c) 2020-2022 Microchip Corporation. All rights reserved.
* Based on {omap2430,tusb6010,ux500}.c
*
*/

#include <linux/clk.h>
#include <linux/dma-mapping.h>
#include <linux/err.h>
#include <linux/io.h>
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/platform_device.h>
#include <linux/usb/usb_phy_generic.h>
#include "musb_core.h"
#include "musb_dma.h"

#define MPFS_MUSB_MAX_EP_NUM 8
#define MPFS_MUSB_RAM_BITS 12

struct mpfs_glue {
struct device *dev;
struct platform_device *musb;
struct platform_device *phy;
struct clk *clk;
};

static struct musb_fifo_cfg mpfs_musb_mode_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 = 1024, },
{ .hw_ep_num = 4, .style = FIFO_RX, .maxpacket = 4096, },
};

static const struct musb_hdrc_config mpfs_musb_hdrc_config = {
.fifo_cfg = mpfs_musb_mode_cfg,
.fifo_cfg_size = ARRAY_SIZE(mpfs_musb_mode_cfg),
.multipoint = true,
.dyn_fifo = true,
.num_eps = MPFS_MUSB_MAX_EP_NUM,
.ram_bits = MPFS_MUSB_RAM_BITS,
};

static irqreturn_t mpfs_musb_interrupt(int irq, void *__hci)
{
unsigned long flags;
irqreturn_t ret = IRQ_NONE;
struct musb *musb = __hci;

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);

if (musb->int_usb || musb->int_tx || musb->int_rx) {
musb_writeb(musb->mregs, MUSB_INTRUSB, musb->int_usb);
musb_writew(musb->mregs, MUSB_INTRTX, musb->int_tx);
musb_writew(musb->mregs, MUSB_INTRRX, musb->int_rx);
ret = musb_interrupt(musb);
}

spin_unlock_irqrestore(&musb->lock, flags);

return ret;
}

static void mpfs_musb_set_vbus(struct musb *musb, int is_on)
{
u8 devctl;

/*
* HDRC controls CPEN, but beware current surges during device
* connect. They can trigger transient overcurrent conditions
* that must be ignored.
*/
devctl = musb_readb(musb->mregs, MUSB_DEVCTL);

if (is_on) {
musb->is_active = 1;
musb->xceiv->otg->default_a = 1;
musb->xceiv->otg->state = OTG_STATE_A_WAIT_VRISE;
devctl |= MUSB_DEVCTL_SESSION;
MUSB_HST_MODE(musb);
} else {
musb->is_active = 0;

/*
* NOTE: skipping A_WAIT_VFALL -> A_IDLE and
* jumping right to B_IDLE...
*/
musb->xceiv->otg->default_a = 0;
musb->xceiv->otg->state = OTG_STATE_B_IDLE;
devctl &= ~MUSB_DEVCTL_SESSION;

MUSB_DEV_MODE(musb);
}

musb_writeb(musb->mregs, MUSB_DEVCTL, devctl);

dev_dbg(musb->controller, "VBUS %s, devctl %02x\n",
usb_otg_state_string(musb->xceiv->otg->state),
musb_readb(musb->mregs, MUSB_DEVCTL));
}

static int mpfs_musb_init(struct musb *musb)
{
struct device *dev = musb->controller;

musb->xceiv = devm_usb_get_phy(dev, USB_PHY_TYPE_USB2);
if (IS_ERR(musb->xceiv)) {
dev_err(dev, "HS UDC: no transceiver configured\n");
return PTR_ERR(musb->xceiv);
}

musb->dyn_fifo = true;
musb->isr = mpfs_musb_interrupt;

musb_platform_set_vbus(musb, 1);

return 0;
}

static const struct musb_platform_ops mpfs_ops = {
.quirks = MUSB_DMA_INVENTRA,
.init = mpfs_musb_init,
.fifo_mode = 2,
#ifdef CONFIG_USB_INVENTRA_DMA
.dma_init = musbhs_dma_controller_create,
.dma_exit = musbhs_dma_controller_destroy,
#endif
.set_vbus = mpfs_musb_set_vbus
};

static int mpfs_probe(struct platform_device *pdev)
{
struct musb_hdrc_platform_data *pdata = dev_get_platdata(&pdev->dev);
struct mpfs_glue *glue;
struct platform_device *musb_pdev;
struct device *dev = &pdev->dev;
struct clk *clk;
int ret;

glue = devm_kzalloc(dev, sizeof(*glue), GFP_KERNEL);
if (!glue)
return -ENOMEM;

musb_pdev = platform_device_alloc("musb-hdrc", PLATFORM_DEVID_AUTO);
if (!musb_pdev) {
dev_err(dev, "failed to allocate musb device\n");
return -ENOMEM;
}

clk = devm_clk_get(&pdev->dev, NULL);
if (IS_ERR(clk)) {
dev_err(&pdev->dev, "failed to get clock\n");
ret = PTR_ERR(clk);
goto err_phy_release;
}

ret = clk_prepare_enable(clk);
if (ret) {
dev_err(&pdev->dev, "failed to enable clock\n");
goto err_phy_release;
}

musb_pdev->dev.parent = dev;
musb_pdev->dev.coherent_dma_mask = DMA_BIT_MASK(39);
musb_pdev->dev.dma_mask = &musb_pdev->dev.coherent_dma_mask;
device_set_of_node_from_dev(&musb_pdev->dev, dev);

glue->dev = dev;
glue->musb = musb_pdev;
glue->clk = clk;

pdata = devm_kzalloc(dev, sizeof(*pdata), GFP_KERNEL);
if (!pdata)
goto err_clk_disable;

pdata->config = &mpfs_musb_hdrc_config;
pdata->platform_ops = &mpfs_ops;

pdata->mode = usb_get_dr_mode(dev);
if (pdata->mode == USB_DR_MODE_UNKNOWN) {
dev_info(dev, "No dr_mode property found, defaulting to otg\n");
pdata->mode = USB_DR_MODE_OTG;
}

glue->phy = usb_phy_generic_register();
if (IS_ERR(glue->phy)) {
dev_err(dev, "failed to register usb-phy %ld\n",
PTR_ERR(glue->phy));
goto err_clk_disable;
}

platform_set_drvdata(pdev, glue);

ret = platform_device_add_resources(musb_pdev, 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_pdev, pdata, sizeof(*pdata));
if (ret) {
dev_err(dev, "failed to add platform_data\n");
goto err_clk_disable;
}

ret = platform_device_add(musb_pdev);
if (ret) {
dev_err(dev, "failed to register musb device\n");
goto err_clk_disable;
}

dev_info(&pdev->dev, "Registered MPFS MUSB driver\n");
return 0;

err_clk_disable:
clk_disable_unprepare(clk);

err_phy_release:
usb_phy_generic_unregister(glue->phy);
platform_device_put(musb_pdev);
return ret;
}

static int mpfs_remove(struct platform_device *pdev)
{
struct mpfs_glue *glue = platform_get_drvdata(pdev);

platform_device_unregister(glue->musb);
usb_phy_generic_unregister(pdev);

return 0;
}

#ifdef CONFIG_OF
static const struct of_device_id mpfs_id_table[] = {
{ .compatible = "microchip,mpfs-musb" },
{ }
};
MODULE_DEVICE_TABLE(of, mpfs_id_table);
#endif

static struct platform_driver mpfs_musb_driver = {
.probe = mpfs_probe,
.remove = mpfs_remove,
.driver = {
.name = "mpfs-musb",
.of_match_table = of_match_ptr(mpfs_id_table)
},
};

module_platform_driver(mpfs_musb_driver);

MODULE_DESCRIPTION("PolarFire SoC MUSB Glue Layer");
MODULE_LICENSE("GPL");

0 comments on commit 7a96b6e

Please sign in to comment.