162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0+
262306a36Sopenharmony_ci/*
362306a36Sopenharmony_ci * c67x00-drv.c: Cypress C67X00 USB Common infrastructure
462306a36Sopenharmony_ci *
562306a36Sopenharmony_ci * Copyright (C) 2006-2008 Barco N.V.
662306a36Sopenharmony_ci *    Derived from the Cypress cy7c67200/300 ezusb linux driver and
762306a36Sopenharmony_ci *    based on multiple host controller drivers inside the linux kernel.
862306a36Sopenharmony_ci */
962306a36Sopenharmony_ci
1062306a36Sopenharmony_ci/*
1162306a36Sopenharmony_ci * This file implements the common infrastructure for using the c67x00.
1262306a36Sopenharmony_ci * It is both the link between the platform configuration and subdrivers and
1362306a36Sopenharmony_ci * the link between the common hardware parts and the subdrivers (e.g.
1462306a36Sopenharmony_ci * interrupt handling).
1562306a36Sopenharmony_ci *
1662306a36Sopenharmony_ci * The c67x00 has 2 SIE's (serial interface engine) which can be configured
1762306a36Sopenharmony_ci * to be host, device or OTG (with some limitations, E.G. only SIE1 can be OTG).
1862306a36Sopenharmony_ci *
1962306a36Sopenharmony_ci * Depending on the platform configuration, the SIE's are created and
2062306a36Sopenharmony_ci * the corresponding subdriver is initialized (c67x00_probe_sie).
2162306a36Sopenharmony_ci */
2262306a36Sopenharmony_ci
2362306a36Sopenharmony_ci#include <linux/device.h>
2462306a36Sopenharmony_ci#include <linux/io.h>
2562306a36Sopenharmony_ci#include <linux/list.h>
2662306a36Sopenharmony_ci#include <linux/slab.h>
2762306a36Sopenharmony_ci#include <linux/module.h>
2862306a36Sopenharmony_ci#include <linux/usb.h>
2962306a36Sopenharmony_ci#include <linux/usb/c67x00.h>
3062306a36Sopenharmony_ci
3162306a36Sopenharmony_ci#include "c67x00.h"
3262306a36Sopenharmony_ci#include "c67x00-hcd.h"
3362306a36Sopenharmony_ci
3462306a36Sopenharmony_cistatic void c67x00_probe_sie(struct c67x00_sie *sie,
3562306a36Sopenharmony_ci			     struct c67x00_device *dev, int sie_num)
3662306a36Sopenharmony_ci{
3762306a36Sopenharmony_ci	spin_lock_init(&sie->lock);
3862306a36Sopenharmony_ci	sie->dev = dev;
3962306a36Sopenharmony_ci	sie->sie_num = sie_num;
4062306a36Sopenharmony_ci	sie->mode = c67x00_sie_config(dev->pdata->sie_config, sie_num);
4162306a36Sopenharmony_ci
4262306a36Sopenharmony_ci	switch (sie->mode) {
4362306a36Sopenharmony_ci	case C67X00_SIE_HOST:
4462306a36Sopenharmony_ci		c67x00_hcd_probe(sie);
4562306a36Sopenharmony_ci		break;
4662306a36Sopenharmony_ci
4762306a36Sopenharmony_ci	case C67X00_SIE_UNUSED:
4862306a36Sopenharmony_ci		dev_info(sie_dev(sie),
4962306a36Sopenharmony_ci			 "Not using SIE %d as requested\n", sie->sie_num);
5062306a36Sopenharmony_ci		break;
5162306a36Sopenharmony_ci
5262306a36Sopenharmony_ci	default:
5362306a36Sopenharmony_ci		dev_err(sie_dev(sie),
5462306a36Sopenharmony_ci			"Unsupported configuration: 0x%x for SIE %d\n",
5562306a36Sopenharmony_ci			sie->mode, sie->sie_num);
5662306a36Sopenharmony_ci		break;
5762306a36Sopenharmony_ci	}
5862306a36Sopenharmony_ci}
5962306a36Sopenharmony_ci
6062306a36Sopenharmony_cistatic void c67x00_remove_sie(struct c67x00_sie *sie)
6162306a36Sopenharmony_ci{
6262306a36Sopenharmony_ci	switch (sie->mode) {
6362306a36Sopenharmony_ci	case C67X00_SIE_HOST:
6462306a36Sopenharmony_ci		c67x00_hcd_remove(sie);
6562306a36Sopenharmony_ci		break;
6662306a36Sopenharmony_ci
6762306a36Sopenharmony_ci	default:
6862306a36Sopenharmony_ci		break;
6962306a36Sopenharmony_ci	}
7062306a36Sopenharmony_ci}
7162306a36Sopenharmony_ci
7262306a36Sopenharmony_cistatic irqreturn_t c67x00_irq(int irq, void *__dev)
7362306a36Sopenharmony_ci{
7462306a36Sopenharmony_ci	struct c67x00_device *c67x00 = __dev;
7562306a36Sopenharmony_ci	struct c67x00_sie *sie;
7662306a36Sopenharmony_ci	u16 msg, int_status;
7762306a36Sopenharmony_ci	int i, count = 8;
7862306a36Sopenharmony_ci
7962306a36Sopenharmony_ci	int_status = c67x00_ll_hpi_status(c67x00);
8062306a36Sopenharmony_ci	if (!int_status)
8162306a36Sopenharmony_ci		return IRQ_NONE;
8262306a36Sopenharmony_ci
8362306a36Sopenharmony_ci	while (int_status != 0 && (count-- >= 0)) {
8462306a36Sopenharmony_ci		c67x00_ll_irq(c67x00, int_status);
8562306a36Sopenharmony_ci		for (i = 0; i < C67X00_SIES; i++) {
8662306a36Sopenharmony_ci			sie = &c67x00->sie[i];
8762306a36Sopenharmony_ci			msg = 0;
8862306a36Sopenharmony_ci			if (int_status & SIEMSG_FLG(i))
8962306a36Sopenharmony_ci				msg = c67x00_ll_fetch_siemsg(c67x00, i);
9062306a36Sopenharmony_ci			if (sie->irq)
9162306a36Sopenharmony_ci				sie->irq(sie, int_status, msg);
9262306a36Sopenharmony_ci		}
9362306a36Sopenharmony_ci		int_status = c67x00_ll_hpi_status(c67x00);
9462306a36Sopenharmony_ci	}
9562306a36Sopenharmony_ci
9662306a36Sopenharmony_ci	if (int_status)
9762306a36Sopenharmony_ci		dev_warn(&c67x00->pdev->dev, "Not all interrupts handled! "
9862306a36Sopenharmony_ci			 "status = 0x%04x\n", int_status);
9962306a36Sopenharmony_ci
10062306a36Sopenharmony_ci	return IRQ_HANDLED;
10162306a36Sopenharmony_ci}
10262306a36Sopenharmony_ci
10362306a36Sopenharmony_ci/* ------------------------------------------------------------------------- */
10462306a36Sopenharmony_ci
10562306a36Sopenharmony_cistatic int c67x00_drv_probe(struct platform_device *pdev)
10662306a36Sopenharmony_ci{
10762306a36Sopenharmony_ci	struct c67x00_device *c67x00;
10862306a36Sopenharmony_ci	struct c67x00_platform_data *pdata;
10962306a36Sopenharmony_ci	struct resource *res, *res2;
11062306a36Sopenharmony_ci	int ret, i;
11162306a36Sopenharmony_ci
11262306a36Sopenharmony_ci	res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
11362306a36Sopenharmony_ci	if (!res)
11462306a36Sopenharmony_ci		return -ENODEV;
11562306a36Sopenharmony_ci
11662306a36Sopenharmony_ci	res2 = platform_get_resource(pdev, IORESOURCE_IRQ, 0);
11762306a36Sopenharmony_ci	if (!res2)
11862306a36Sopenharmony_ci		return -ENODEV;
11962306a36Sopenharmony_ci
12062306a36Sopenharmony_ci	pdata = dev_get_platdata(&pdev->dev);
12162306a36Sopenharmony_ci	if (!pdata)
12262306a36Sopenharmony_ci		return -ENODEV;
12362306a36Sopenharmony_ci
12462306a36Sopenharmony_ci	c67x00 = kzalloc(sizeof(*c67x00), GFP_KERNEL);
12562306a36Sopenharmony_ci	if (!c67x00)
12662306a36Sopenharmony_ci		return -ENOMEM;
12762306a36Sopenharmony_ci
12862306a36Sopenharmony_ci	if (!request_mem_region(res->start, resource_size(res),
12962306a36Sopenharmony_ci				pdev->name)) {
13062306a36Sopenharmony_ci		dev_err(&pdev->dev, "Memory region busy\n");
13162306a36Sopenharmony_ci		ret = -EBUSY;
13262306a36Sopenharmony_ci		goto request_mem_failed;
13362306a36Sopenharmony_ci	}
13462306a36Sopenharmony_ci	c67x00->hpi.base = ioremap(res->start, resource_size(res));
13562306a36Sopenharmony_ci	if (!c67x00->hpi.base) {
13662306a36Sopenharmony_ci		dev_err(&pdev->dev, "Unable to map HPI registers\n");
13762306a36Sopenharmony_ci		ret = -EIO;
13862306a36Sopenharmony_ci		goto map_failed;
13962306a36Sopenharmony_ci	}
14062306a36Sopenharmony_ci
14162306a36Sopenharmony_ci	spin_lock_init(&c67x00->hpi.lock);
14262306a36Sopenharmony_ci	c67x00->hpi.regstep = pdata->hpi_regstep;
14362306a36Sopenharmony_ci	c67x00->pdata = dev_get_platdata(&pdev->dev);
14462306a36Sopenharmony_ci	c67x00->pdev = pdev;
14562306a36Sopenharmony_ci
14662306a36Sopenharmony_ci	c67x00_ll_init(c67x00);
14762306a36Sopenharmony_ci	c67x00_ll_hpi_reg_init(c67x00);
14862306a36Sopenharmony_ci
14962306a36Sopenharmony_ci	ret = request_irq(res2->start, c67x00_irq, 0, pdev->name, c67x00);
15062306a36Sopenharmony_ci	if (ret) {
15162306a36Sopenharmony_ci		dev_err(&pdev->dev, "Cannot claim IRQ\n");
15262306a36Sopenharmony_ci		goto request_irq_failed;
15362306a36Sopenharmony_ci	}
15462306a36Sopenharmony_ci
15562306a36Sopenharmony_ci	ret = c67x00_ll_reset(c67x00);
15662306a36Sopenharmony_ci	if (ret) {
15762306a36Sopenharmony_ci		dev_err(&pdev->dev, "Device reset failed\n");
15862306a36Sopenharmony_ci		goto reset_failed;
15962306a36Sopenharmony_ci	}
16062306a36Sopenharmony_ci
16162306a36Sopenharmony_ci	for (i = 0; i < C67X00_SIES; i++)
16262306a36Sopenharmony_ci		c67x00_probe_sie(&c67x00->sie[i], c67x00, i);
16362306a36Sopenharmony_ci
16462306a36Sopenharmony_ci	platform_set_drvdata(pdev, c67x00);
16562306a36Sopenharmony_ci
16662306a36Sopenharmony_ci	return 0;
16762306a36Sopenharmony_ci
16862306a36Sopenharmony_ci reset_failed:
16962306a36Sopenharmony_ci	free_irq(res2->start, c67x00);
17062306a36Sopenharmony_ci request_irq_failed:
17162306a36Sopenharmony_ci	iounmap(c67x00->hpi.base);
17262306a36Sopenharmony_ci map_failed:
17362306a36Sopenharmony_ci	release_mem_region(res->start, resource_size(res));
17462306a36Sopenharmony_ci request_mem_failed:
17562306a36Sopenharmony_ci	kfree(c67x00);
17662306a36Sopenharmony_ci
17762306a36Sopenharmony_ci	return ret;
17862306a36Sopenharmony_ci}
17962306a36Sopenharmony_ci
18062306a36Sopenharmony_cistatic void c67x00_drv_remove(struct platform_device *pdev)
18162306a36Sopenharmony_ci{
18262306a36Sopenharmony_ci	struct c67x00_device *c67x00 = platform_get_drvdata(pdev);
18362306a36Sopenharmony_ci	struct resource *res;
18462306a36Sopenharmony_ci	int i;
18562306a36Sopenharmony_ci
18662306a36Sopenharmony_ci	for (i = 0; i < C67X00_SIES; i++)
18762306a36Sopenharmony_ci		c67x00_remove_sie(&c67x00->sie[i]);
18862306a36Sopenharmony_ci
18962306a36Sopenharmony_ci	c67x00_ll_release(c67x00);
19062306a36Sopenharmony_ci
19162306a36Sopenharmony_ci	res = platform_get_resource(pdev, IORESOURCE_IRQ, 0);
19262306a36Sopenharmony_ci	free_irq(res->start, c67x00);
19362306a36Sopenharmony_ci
19462306a36Sopenharmony_ci	iounmap(c67x00->hpi.base);
19562306a36Sopenharmony_ci
19662306a36Sopenharmony_ci	res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
19762306a36Sopenharmony_ci	release_mem_region(res->start, resource_size(res));
19862306a36Sopenharmony_ci
19962306a36Sopenharmony_ci	kfree(c67x00);
20062306a36Sopenharmony_ci}
20162306a36Sopenharmony_ci
20262306a36Sopenharmony_cistatic struct platform_driver c67x00_driver = {
20362306a36Sopenharmony_ci	.probe	= c67x00_drv_probe,
20462306a36Sopenharmony_ci	.remove_new = c67x00_drv_remove,
20562306a36Sopenharmony_ci	.driver	= {
20662306a36Sopenharmony_ci		.name = "c67x00",
20762306a36Sopenharmony_ci	},
20862306a36Sopenharmony_ci};
20962306a36Sopenharmony_ci
21062306a36Sopenharmony_cimodule_platform_driver(c67x00_driver);
21162306a36Sopenharmony_ci
21262306a36Sopenharmony_ciMODULE_AUTHOR("Peter Korsgaard, Jan Veldeman, Grant Likely");
21362306a36Sopenharmony_ciMODULE_DESCRIPTION("Cypress C67X00 USB Controller Driver");
21462306a36Sopenharmony_ciMODULE_LICENSE("GPL");
21562306a36Sopenharmony_ciMODULE_ALIAS("platform:c67x00");
216