root/drivers/usb/gadget/udc/bdc/bdc_core.c
// SPDX-License-Identifier: GPL-2.0+
/*
 * bdc_core.c - BRCM BDC USB3.0 device controller core operations
 *
 * Copyright (C) 2014 Broadcom Corporation
 *
 * Author: Ashwini Pahuja
 */
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/slab.h>
#include <linux/spinlock.h>
#include <linux/platform_device.h>
#include <linux/interrupt.h>
#include <linux/iopoll.h>
#include <linux/ioport.h>
#include <linux/io.h>
#include <linux/list.h>
#include <linux/delay.h>
#include <linux/dma-mapping.h>
#include <linux/dmapool.h>
#include <linux/of.h>
#include <linux/phy/phy.h>
#include <linux/moduleparam.h>
#include <linux/usb/ch9.h>
#include <linux/usb/gadget.h>
#include <linux/clk.h>

#include "bdc.h"
#include "bdc_dbg.h"

/* Poll till controller status is not OIP */
static int poll_oip(struct bdc *bdc, u32 usec)
{
        u32 status;
        int ret;

        ret = readl_poll_timeout_atomic(bdc->regs + BDC_BDCSC, status,
                                        (BDC_CSTS(status) != BDC_OIP), 10, usec);
        if (ret)
                dev_err(bdc->dev, "operation timedout BDCSC: 0x%08x\n", status);
        else
                dev_dbg(bdc->dev, "%s complete status=%d", __func__, BDC_CSTS(status));

        return ret;
}

/* Stop the BDC controller */
int bdc_stop(struct bdc *bdc)
{
        int ret;
        u32 temp;

        dev_dbg(bdc->dev, "%s ()\n\n", __func__);
        temp = bdc_readl(bdc->regs, BDC_BDCSC);
        /* Check if BDC is already halted */
        if (BDC_CSTS(temp) == BDC_HLT) {
                dev_vdbg(bdc->dev, "BDC already halted\n");
                return 0;
        }
        temp &= ~BDC_COP_MASK;
        temp |= BDC_COS|BDC_COP_STP;
        bdc_writel(bdc->regs, BDC_BDCSC, temp);

        ret = poll_oip(bdc, BDC_COP_TIMEOUT);
        if (ret)
                dev_err(bdc->dev, "bdc stop operation failed");

        return ret;
}

/* Issue a reset to BDC controller */
int bdc_reset(struct bdc *bdc)
{
        u32 temp;
        int ret;

        dev_dbg(bdc->dev, "%s ()\n", __func__);
        /* First halt the controller */
        ret = bdc_stop(bdc);
        if (ret)
                return ret;

        temp = bdc_readl(bdc->regs, BDC_BDCSC);
        temp &= ~BDC_COP_MASK;
        temp |= BDC_COS|BDC_COP_RST;
        bdc_writel(bdc->regs, BDC_BDCSC, temp);
        ret = poll_oip(bdc, BDC_COP_TIMEOUT);
        if (ret)
                dev_err(bdc->dev, "bdc reset operation failed");

        return ret;
}

/* Run the BDC controller */
int bdc_run(struct bdc *bdc)
{
        u32 temp;
        int ret;

        dev_dbg(bdc->dev, "%s ()\n", __func__);
        temp = bdc_readl(bdc->regs, BDC_BDCSC);
        /* if BDC is already in running state then do not do anything */
        if (BDC_CSTS(temp) == BDC_NOR) {
                dev_warn(bdc->dev, "bdc is already in running state\n");
                return 0;
        }
        temp &= ~BDC_COP_MASK;
        temp |= BDC_COP_RUN;
        temp |= BDC_COS;
        bdc_writel(bdc->regs, BDC_BDCSC, temp);
        ret = poll_oip(bdc, BDC_COP_TIMEOUT);
        if (ret) {
                dev_err(bdc->dev, "bdc run operation failed:%d", ret);
                return ret;
        }
        temp = bdc_readl(bdc->regs, BDC_BDCSC);
        if (BDC_CSTS(temp) != BDC_NOR) {
                dev_err(bdc->dev, "bdc not in normal mode after RUN op :%d\n",
                                                                BDC_CSTS(temp));
                return -ESHUTDOWN;
        }

        return 0;
}

/*
 * Present the termination to the host, typically called from upstream port
 * event with Vbus present =1
 */
void bdc_softconn(struct bdc *bdc)
{
        u32 uspc;

        uspc = bdc_readl(bdc->regs, BDC_USPC);
        uspc &= ~BDC_PST_MASK;
        uspc |= BDC_LINK_STATE_RX_DET;
        uspc |= BDC_SWS;
        dev_dbg(bdc->dev, "%s () uspc=%08x\n", __func__, uspc);
        bdc_writel(bdc->regs, BDC_USPC, uspc);
}

/* Remove the termination */
void bdc_softdisconn(struct bdc *bdc)
{
        u32 uspc;

        uspc = bdc_readl(bdc->regs, BDC_USPC);
        uspc |= BDC_SDC;
        uspc &= ~BDC_SCN;
        dev_dbg(bdc->dev, "%s () uspc=%x\n", __func__, uspc);
        bdc_writel(bdc->regs, BDC_USPC, uspc);
}

/* Set up the scratchpad buffer array and scratchpad buffers, if needed. */
static int scratchpad_setup(struct bdc *bdc)
{
        int sp_buff_size;
        u32 low32;
        u32 upp32;

        sp_buff_size = BDC_SPB(bdc_readl(bdc->regs, BDC_BDCCFG0));
        dev_dbg(bdc->dev, "%s() sp_buff_size=%d\n", __func__, sp_buff_size);
        if (!sp_buff_size) {
                dev_dbg(bdc->dev, "Scratchpad buffer not needed\n");
                return 0;
        }
        /* Refer to BDC spec, Table 4 for description of SPB */
        sp_buff_size = 1 << (sp_buff_size + 5);
        dev_dbg(bdc->dev, "Allocating %d bytes for scratchpad\n", sp_buff_size);
        bdc->scratchpad.buff  =  dma_alloc_coherent(bdc->dev, sp_buff_size,
                                                    &bdc->scratchpad.sp_dma,
                                                    GFP_KERNEL);

        if (!bdc->scratchpad.buff)
                goto fail;

        bdc->sp_buff_size = sp_buff_size;
        bdc->scratchpad.size = sp_buff_size;
        low32 = lower_32_bits(bdc->scratchpad.sp_dma);
        upp32 = upper_32_bits(bdc->scratchpad.sp_dma);
        cpu_to_le32s(&low32);
        cpu_to_le32s(&upp32);
        bdc_writel(bdc->regs, BDC_SPBBAL, low32);
        bdc_writel(bdc->regs, BDC_SPBBAH, upp32);
        return 0;

fail:
        bdc->scratchpad.buff = NULL;

        return -ENOMEM;
}

/* Allocate the status report ring */
static int setup_srr(struct bdc *bdc, int interrupter)
{
        dev_dbg(bdc->dev, "%s() NUM_SR_ENTRIES:%d\n", __func__, NUM_SR_ENTRIES);
        /* Reset the SRR */
        bdc_writel(bdc->regs, BDC_SRRINT(0), BDC_SRR_RWS | BDC_SRR_RST);
        bdc->srr.dqp_index = 0;
        /* allocate the status report descriptors */
        bdc->srr.sr_bds = dma_alloc_coherent(bdc->dev,
                                             NUM_SR_ENTRIES * sizeof(struct bdc_bd),
                                             &bdc->srr.dma_addr, GFP_KERNEL);
        if (!bdc->srr.sr_bds)
                return -ENOMEM;

        return 0;
}

/* Initialize the HW regs and internal data structures */
static void bdc_mem_init(struct bdc *bdc, bool reinit)
{
        u8 size = 0;
        u32 usb2_pm;
        u32 low32;
        u32 upp32;
        u32 temp;

        dev_dbg(bdc->dev, "%s ()\n", __func__);
        bdc->ep0_state = WAIT_FOR_SETUP;
        bdc->dev_addr = 0;
        bdc->srr.eqp_index = 0;
        bdc->srr.dqp_index = 0;
        bdc->zlp_needed = false;
        bdc->delayed_status = false;

        bdc_writel(bdc->regs, BDC_SPBBAL, bdc->scratchpad.sp_dma);
        /* Init the SRR */
        temp = BDC_SRR_RWS | BDC_SRR_RST;
        /* Reset the SRR */
        bdc_writel(bdc->regs, BDC_SRRINT(0), temp);
        dev_dbg(bdc->dev, "bdc->srr.sr_bds =%p\n", bdc->srr.sr_bds);
        temp = lower_32_bits(bdc->srr.dma_addr);
        size = fls(NUM_SR_ENTRIES) - 2;
        temp |= size;
        dev_dbg(bdc->dev, "SRRBAL[0]=%08x NUM_SR_ENTRIES:%d size:%d\n",
                                                temp, NUM_SR_ENTRIES, size);

        low32 = lower_32_bits(temp);
        upp32 = upper_32_bits(bdc->srr.dma_addr);
        cpu_to_le32s(&low32);
        cpu_to_le32s(&upp32);

        /* Write the dma addresses into regs*/
        bdc_writel(bdc->regs, BDC_SRRBAL(0), low32);
        bdc_writel(bdc->regs, BDC_SRRBAH(0), upp32);

        temp = bdc_readl(bdc->regs, BDC_SRRINT(0));
        temp |= BDC_SRR_IE;
        temp &= ~(BDC_SRR_RST | BDC_SRR_RWS);
        bdc_writel(bdc->regs, BDC_SRRINT(0), temp);

        /* Set the Interrupt Coalescence ~500 usec */
        temp = bdc_readl(bdc->regs, BDC_INTCTLS(0));
        temp &= ~0xffff;
        temp |= INT_CLS;
        bdc_writel(bdc->regs, BDC_INTCTLS(0), temp);

        usb2_pm = bdc_readl(bdc->regs, BDC_USPPM2);
        dev_dbg(bdc->dev, "usb2_pm=%08x", usb2_pm);
        /* Enable hardware LPM Enable */
        usb2_pm |= BDC_HLE;
        bdc_writel(bdc->regs, BDC_USPPM2, usb2_pm);

        /* readback for debug */
        usb2_pm = bdc_readl(bdc->regs, BDC_USPPM2);
        dev_dbg(bdc->dev, "usb2_pm=%08x\n", usb2_pm);

        /* Disable any unwanted SR's on SRR */
        temp = bdc_readl(bdc->regs, BDC_BDCSC);
        /* We don't want Microframe counter wrap SR */
        temp |= BDC_MASK_MCW;
        bdc_writel(bdc->regs, BDC_BDCSC, temp);

        /*
         * In some error cases, driver has to reset the entire BDC controller
         * in that case reinit is passed as 1
         */
        if (reinit) {
                int i;
                /* Enable interrupts */
                temp = bdc_readl(bdc->regs, BDC_BDCSC);
                temp |= BDC_GIE;
                bdc_writel(bdc->regs, BDC_BDCSC, temp);
                /* Init scratchpad to 0 */
                memset(bdc->scratchpad.buff, 0, bdc->sp_buff_size);
                /* Initialize SRR to 0 */
                memset(bdc->srr.sr_bds, 0,
                                        NUM_SR_ENTRIES * sizeof(struct bdc_bd));
                /*
                 * clear ep flags to avoid post disconnect stops/deconfigs but
                 * not during S2 exit
                 */
                if (!bdc->gadget.speed)
                        for (i = 1; i < bdc->num_eps; ++i)
                                bdc->bdc_ep_array[i]->flags = 0;
        } else {
                /* One time initiaization only */
                /* Enable status report function pointers */
                bdc->sr_handler[0] = bdc_sr_xsf;
                bdc->sr_handler[1] = bdc_sr_uspc;

                /* EP0 status report function pointers */
                bdc->sr_xsf_ep0[0] = bdc_xsf_ep0_setup_recv;
                bdc->sr_xsf_ep0[1] = bdc_xsf_ep0_data_start;
                bdc->sr_xsf_ep0[2] = bdc_xsf_ep0_status_start;
        }
}

/* Free the dynamic memory */
static void bdc_mem_free(struct bdc *bdc)
{
        dev_dbg(bdc->dev, "%s\n", __func__);
        /* Free SRR */
        if (bdc->srr.sr_bds)
                dma_free_coherent(bdc->dev,
                                        NUM_SR_ENTRIES * sizeof(struct bdc_bd),
                                        bdc->srr.sr_bds, bdc->srr.dma_addr);

        /* Free scratchpad */
        if (bdc->scratchpad.buff)
                dma_free_coherent(bdc->dev, bdc->sp_buff_size,
                                bdc->scratchpad.buff, bdc->scratchpad.sp_dma);

        /* Destroy the dma pools */
        dma_pool_destroy(bdc->bd_table_pool);

        /* Free the bdc_ep array */
        kfree(bdc->bdc_ep_array);

        bdc->srr.sr_bds = NULL;
        bdc->scratchpad.buff = NULL;
        bdc->bd_table_pool = NULL;
        bdc->bdc_ep_array = NULL;
}

/*
 * bdc reinit gives a controller reset and reinitialize the registers,
 * called from disconnect/bus reset scenario's, to ensure proper HW cleanup
 */
int bdc_reinit(struct bdc *bdc)
{
        int ret;

        dev_dbg(bdc->dev, "%s\n", __func__);
        ret = bdc_stop(bdc);
        if (ret)
                goto out;

        ret = bdc_reset(bdc);
        if (ret)
                goto out;

        /* the reinit flag is 1 */
        bdc_mem_init(bdc, true);
        ret = bdc_run(bdc);
out:
        bdc->reinit = false;

        return ret;
}

/* Allocate all the dyanmic memory */
static int bdc_mem_alloc(struct bdc *bdc)
{
        u32 page_size;
        unsigned int num_ieps, num_oeps;

        dev_dbg(bdc->dev,
                "%s() NUM_BDS_PER_TABLE:%d\n", __func__,
                NUM_BDS_PER_TABLE);
        page_size = BDC_PGS(bdc_readl(bdc->regs, BDC_BDCCFG0));
        /* page size is 2^pgs KB */
        page_size = 1 << page_size;
        /* KB */
        page_size <<= 10;
        dev_dbg(bdc->dev, "page_size=%d\n", page_size);

        /* Create a pool of bd tables */
        bdc->bd_table_pool =
            dma_pool_create("BDC BD tables", bdc->dev, NUM_BDS_PER_TABLE * 16,
                                                                16, page_size);

        if (!bdc->bd_table_pool)
                goto fail;

        if (scratchpad_setup(bdc))
                goto fail;

        /* read from regs */
        num_ieps = NUM_NCS(bdc_readl(bdc->regs, BDC_FSCNIC));
        num_oeps = NUM_NCS(bdc_readl(bdc->regs, BDC_FSCNOC));
        /* +2: 1 for ep0 and the other is rsvd i.e. bdc_ep[0] is rsvd */
        bdc->num_eps = num_ieps + num_oeps + 2;
        dev_dbg(bdc->dev,
                "ieps:%d eops:%d num_eps:%d\n",
                num_ieps, num_oeps, bdc->num_eps);
        /* allocate array of ep pointers */
        bdc->bdc_ep_array = kzalloc_objs(struct bdc_ep *, bdc->num_eps);
        if (!bdc->bdc_ep_array)
                goto fail;

        dev_dbg(bdc->dev, "Allocating sr report0\n");
        if (setup_srr(bdc, 0))
                goto fail;

        return 0;
fail:
        dev_warn(bdc->dev, "Couldn't initialize memory\n");
        bdc_mem_free(bdc);

        return -ENOMEM;
}

/* opposite to bdc_hw_init */
static void bdc_hw_exit(struct bdc *bdc)
{
        dev_dbg(bdc->dev, "%s ()\n", __func__);
        bdc_mem_free(bdc);
}

/* Initialize the bdc HW and memory */
static int bdc_hw_init(struct bdc *bdc)
{
        int ret;

        dev_dbg(bdc->dev, "%s ()\n", __func__);
        ret = bdc_reset(bdc);
        if (ret) {
                dev_err(bdc->dev, "err resetting bdc abort bdc init%d\n", ret);
                return ret;
        }
        ret = bdc_mem_alloc(bdc);
        if (ret) {
                dev_err(bdc->dev, "Mem alloc failed, aborting\n");
                return -ENOMEM;
        }
        bdc_mem_init(bdc, 0);
        bdc_dbg_regs(bdc);
        dev_dbg(bdc->dev, "HW Init done\n");

        return 0;
}

static int bdc_phy_init(struct bdc *bdc)
{
        int phy_num;
        int ret;

        for (phy_num = 0; phy_num < bdc->num_phys; phy_num++) {
                ret = phy_init(bdc->phys[phy_num]);
                if (ret)
                        goto err_exit_phy;
                ret = phy_power_on(bdc->phys[phy_num]);
                if (ret) {
                        phy_exit(bdc->phys[phy_num]);
                        goto err_exit_phy;
                }
        }

        return 0;

err_exit_phy:
        while (--phy_num >= 0) {
                phy_power_off(bdc->phys[phy_num]);
                phy_exit(bdc->phys[phy_num]);
        }

        return ret;
}

static void bdc_phy_exit(struct bdc *bdc)
{
        int phy_num;

        for (phy_num = 0; phy_num < bdc->num_phys; phy_num++) {
                phy_power_off(bdc->phys[phy_num]);
                phy_exit(bdc->phys[phy_num]);
        }
}

static int bdc_probe(struct platform_device *pdev)
{
        struct bdc *bdc;
        int ret;
        int irq;
        u32 temp;
        struct device *dev = &pdev->dev;
        int phy_num;

        dev_dbg(dev, "%s()\n", __func__);

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

        bdc->regs = devm_platform_ioremap_resource(pdev, 0);
        if (IS_ERR(bdc->regs))
                return PTR_ERR(bdc->regs);

        irq = platform_get_irq(pdev, 0);
        if (irq < 0)
                return irq;
        spin_lock_init(&bdc->lock);
        platform_set_drvdata(pdev, bdc);
        bdc->irq = irq;
        bdc->dev = dev;
        dev_dbg(dev, "bdc->regs: %p irq=%d\n", bdc->regs, bdc->irq);

        bdc->num_phys = of_count_phandle_with_args(dev->of_node,
                                                "phys", "#phy-cells");
        if (bdc->num_phys > 0) {
                bdc->phys = devm_kcalloc(dev, bdc->num_phys,
                                        sizeof(struct phy *), GFP_KERNEL);
                if (!bdc->phys)
                        return -ENOMEM;
        } else {
                bdc->num_phys = 0;
        }
        dev_info(dev, "Using %d phy(s)\n", bdc->num_phys);

        for (phy_num = 0; phy_num < bdc->num_phys; phy_num++) {
                bdc->phys[phy_num] = devm_of_phy_get_by_index(
                        dev, dev->of_node, phy_num);
                if (IS_ERR(bdc->phys[phy_num])) {
                        ret = PTR_ERR(bdc->phys[phy_num]);
                        dev_err(bdc->dev,
                                "BDC phy specified but not found:%d\n", ret);
                        return ret;
                }
        }

        bdc->clk = devm_clk_get_optional(dev, "sw_usbd");
        if (IS_ERR(bdc->clk))
                return PTR_ERR(bdc->clk);

        ret = clk_prepare_enable(bdc->clk);
        if (ret) {
                dev_err(dev, "could not enable clock\n");
                return ret;
        }

        ret = bdc_phy_init(bdc);
        if (ret) {
                dev_err(bdc->dev, "BDC phy init failure:%d\n", ret);
                goto disable_clk;
        }

        temp = bdc_readl(bdc->regs, BDC_BDCCAP1);
        if ((temp & BDC_P64) &&
                        !dma_set_mask_and_coherent(dev, DMA_BIT_MASK(64))) {
                dev_dbg(dev, "Using 64-bit address\n");
        } else {
                ret = dma_set_mask_and_coherent(dev, DMA_BIT_MASK(32));
                if (ret) {
                        dev_err(dev,
                                "No suitable DMA config available, abort\n");
                        ret = -ENOTSUPP;
                        goto phycleanup;
                }
                dev_dbg(dev, "Using 32-bit address\n");
        }
        ret = bdc_hw_init(bdc);
        if (ret) {
                dev_err(dev, "BDC init failure:%d\n", ret);
                goto phycleanup;
        }
        ret = bdc_udc_init(bdc);
        if (ret) {
                dev_err(dev, "BDC Gadget init failure:%d\n", ret);
                goto cleanup;
        }
        return 0;

cleanup:
        bdc_hw_exit(bdc);
phycleanup:
        bdc_phy_exit(bdc);
disable_clk:
        clk_disable_unprepare(bdc->clk);
        return ret;
}

static void bdc_remove(struct platform_device *pdev)
{
        struct bdc *bdc;

        bdc  = platform_get_drvdata(pdev);
        dev_dbg(bdc->dev, "%s ()\n", __func__);
        bdc_udc_exit(bdc);
        bdc_hw_exit(bdc);
        bdc_phy_exit(bdc);
        clk_disable_unprepare(bdc->clk);
}

#ifdef CONFIG_PM_SLEEP
static int bdc_suspend(struct device *dev)
{
        struct bdc *bdc = dev_get_drvdata(dev);
        int ret;

        /* Halt the controller */
        ret = bdc_stop(bdc);
        if (!ret)
                clk_disable_unprepare(bdc->clk);

        return ret;
}

static int bdc_resume(struct device *dev)
{
        struct bdc *bdc = dev_get_drvdata(dev);
        int ret;

        ret = clk_prepare_enable(bdc->clk);
        if (ret) {
                dev_err(bdc->dev, "err enabling the clock\n");
                return ret;
        }
        ret = bdc_reinit(bdc);
        if (ret) {
                dev_err(bdc->dev, "err in bdc reinit\n");
                clk_disable_unprepare(bdc->clk);
                return ret;
        }

        return 0;
}

#endif /* CONFIG_PM_SLEEP */

static SIMPLE_DEV_PM_OPS(bdc_pm_ops, bdc_suspend,
                bdc_resume);

static const struct of_device_id bdc_of_match[] = {
        { .compatible = "brcm,bdc-udc-v2" },
        { .compatible = "brcm,bdc" },
        { /* sentinel */ }
};
MODULE_DEVICE_TABLE(of, bdc_of_match);

static struct platform_driver bdc_driver = {
        .driver         = {
                .name   = BRCM_BDC_NAME,
                .pm = &bdc_pm_ops,
                .of_match_table = bdc_of_match,
        },
        .probe          = bdc_probe,
        .remove         = bdc_remove,
};

module_platform_driver(bdc_driver);
MODULE_AUTHOR("Ashwini Pahuja <ashwini.linux@gmail.com>");
MODULE_LICENSE("GPL");
MODULE_DESCRIPTION(BRCM_BDC_DESC);