162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-or-later
262306a36Sopenharmony_ci/*
362306a36Sopenharmony_ci * omap_cf.c -- OMAP 16xx CompactFlash controller driver
462306a36Sopenharmony_ci *
562306a36Sopenharmony_ci * Copyright (c) 2005 David Brownell
662306a36Sopenharmony_ci */
762306a36Sopenharmony_ci
862306a36Sopenharmony_ci#include <linux/module.h>
962306a36Sopenharmony_ci#include <linux/kernel.h>
1062306a36Sopenharmony_ci#include <linux/platform_device.h>
1162306a36Sopenharmony_ci#include <linux/errno.h>
1262306a36Sopenharmony_ci#include <linux/init.h>
1362306a36Sopenharmony_ci#include <linux/delay.h>
1462306a36Sopenharmony_ci#include <linux/interrupt.h>
1562306a36Sopenharmony_ci#include <linux/slab.h>
1662306a36Sopenharmony_ci
1762306a36Sopenharmony_ci#include <pcmcia/ss.h>
1862306a36Sopenharmony_ci
1962306a36Sopenharmony_ci#include <asm/io.h>
2062306a36Sopenharmony_ci#include <linux/sizes.h>
2162306a36Sopenharmony_ci
2262306a36Sopenharmony_ci#include <linux/soc/ti/omap1-io.h>
2362306a36Sopenharmony_ci#include <linux/soc/ti/omap1-soc.h>
2462306a36Sopenharmony_ci#include <linux/soc/ti/omap1-mux.h>
2562306a36Sopenharmony_ci
2662306a36Sopenharmony_ci/* NOTE:  don't expect this to support many I/O cards.  The 16xx chips have
2762306a36Sopenharmony_ci * hard-wired timings to support Compact Flash memory cards; they won't work
2862306a36Sopenharmony_ci * with various other devices (like WLAN adapters) without some external
2962306a36Sopenharmony_ci * logic to help out.
3062306a36Sopenharmony_ci *
3162306a36Sopenharmony_ci * NOTE:  CF controller docs disagree with address space docs as to where
3262306a36Sopenharmony_ci * CF_BASE really lives; this is a doc erratum.
3362306a36Sopenharmony_ci */
3462306a36Sopenharmony_ci#define	CF_BASE	0xfffe2800
3562306a36Sopenharmony_ci
3662306a36Sopenharmony_ci/* status; read after IRQ */
3762306a36Sopenharmony_ci#define CF_STATUS			(CF_BASE + 0x00)
3862306a36Sopenharmony_ci#	define	CF_STATUS_BAD_READ	(1 << 2)
3962306a36Sopenharmony_ci#	define	CF_STATUS_BAD_WRITE	(1 << 1)
4062306a36Sopenharmony_ci#	define	CF_STATUS_CARD_DETECT	(1 << 0)
4162306a36Sopenharmony_ci
4262306a36Sopenharmony_ci/* which chipselect (CS0..CS3) is used for CF (active low) */
4362306a36Sopenharmony_ci#define CF_CFG				(CF_BASE + 0x02)
4462306a36Sopenharmony_ci
4562306a36Sopenharmony_ci/* card reset */
4662306a36Sopenharmony_ci#define CF_CONTROL			(CF_BASE + 0x04)
4762306a36Sopenharmony_ci#	define	CF_CONTROL_RESET	(1 << 0)
4862306a36Sopenharmony_ci
4962306a36Sopenharmony_ci#define omap_cf_present() (!(omap_readw(CF_STATUS) & CF_STATUS_CARD_DETECT))
5062306a36Sopenharmony_ci
5162306a36Sopenharmony_ci/*--------------------------------------------------------------------------*/
5262306a36Sopenharmony_ci
5362306a36Sopenharmony_cistatic const char driver_name[] = "omap_cf";
5462306a36Sopenharmony_ci
5562306a36Sopenharmony_cistruct omap_cf_socket {
5662306a36Sopenharmony_ci	struct pcmcia_socket	socket;
5762306a36Sopenharmony_ci
5862306a36Sopenharmony_ci	struct timer_list	timer;
5962306a36Sopenharmony_ci	unsigned		present:1;
6062306a36Sopenharmony_ci	unsigned		active:1;
6162306a36Sopenharmony_ci
6262306a36Sopenharmony_ci	struct platform_device	*pdev;
6362306a36Sopenharmony_ci	unsigned long		phys_cf;
6462306a36Sopenharmony_ci	u_int			irq;
6562306a36Sopenharmony_ci	struct resource		iomem;
6662306a36Sopenharmony_ci};
6762306a36Sopenharmony_ci
6862306a36Sopenharmony_ci#define	POLL_INTERVAL		(2 * HZ)
6962306a36Sopenharmony_ci
7062306a36Sopenharmony_ci/*--------------------------------------------------------------------------*/
7162306a36Sopenharmony_ci
7262306a36Sopenharmony_cistatic int omap_cf_ss_init(struct pcmcia_socket *s)
7362306a36Sopenharmony_ci{
7462306a36Sopenharmony_ci	return 0;
7562306a36Sopenharmony_ci}
7662306a36Sopenharmony_ci
7762306a36Sopenharmony_ci/* the timer is primarily to kick this socket's pccardd */
7862306a36Sopenharmony_cistatic void omap_cf_timer(struct timer_list *t)
7962306a36Sopenharmony_ci{
8062306a36Sopenharmony_ci	struct omap_cf_socket	*cf = from_timer(cf, t, timer);
8162306a36Sopenharmony_ci	unsigned		present = omap_cf_present();
8262306a36Sopenharmony_ci
8362306a36Sopenharmony_ci	if (present != cf->present) {
8462306a36Sopenharmony_ci		cf->present = present;
8562306a36Sopenharmony_ci		pr_debug("%s: card %s\n", driver_name,
8662306a36Sopenharmony_ci			present ? "present" : "gone");
8762306a36Sopenharmony_ci		pcmcia_parse_events(&cf->socket, SS_DETECT);
8862306a36Sopenharmony_ci	}
8962306a36Sopenharmony_ci
9062306a36Sopenharmony_ci	if (cf->active)
9162306a36Sopenharmony_ci		mod_timer(&cf->timer, jiffies + POLL_INTERVAL);
9262306a36Sopenharmony_ci}
9362306a36Sopenharmony_ci
9462306a36Sopenharmony_ci/* This irq handler prevents "irqNNN: nobody cared" messages as drivers
9562306a36Sopenharmony_ci * claim the card's IRQ.  It may also detect some card insertions, but
9662306a36Sopenharmony_ci * not removals; it can't always eliminate timer irqs.
9762306a36Sopenharmony_ci */
9862306a36Sopenharmony_cistatic irqreturn_t omap_cf_irq(int irq, void *_cf)
9962306a36Sopenharmony_ci{
10062306a36Sopenharmony_ci	struct omap_cf_socket *cf = (struct omap_cf_socket *)_cf;
10162306a36Sopenharmony_ci
10262306a36Sopenharmony_ci	omap_cf_timer(&cf->timer);
10362306a36Sopenharmony_ci	return IRQ_HANDLED;
10462306a36Sopenharmony_ci}
10562306a36Sopenharmony_ci
10662306a36Sopenharmony_cistatic int omap_cf_get_status(struct pcmcia_socket *s, u_int *sp)
10762306a36Sopenharmony_ci{
10862306a36Sopenharmony_ci	if (!sp)
10962306a36Sopenharmony_ci		return -EINVAL;
11062306a36Sopenharmony_ci
11162306a36Sopenharmony_ci	/* NOTE CF is always 3VCARD */
11262306a36Sopenharmony_ci	if (omap_cf_present()) {
11362306a36Sopenharmony_ci		struct omap_cf_socket	*cf;
11462306a36Sopenharmony_ci
11562306a36Sopenharmony_ci		*sp = SS_READY | SS_DETECT | SS_POWERON | SS_3VCARD;
11662306a36Sopenharmony_ci		cf = container_of(s, struct omap_cf_socket, socket);
11762306a36Sopenharmony_ci		s->pcmcia_irq = 0;
11862306a36Sopenharmony_ci		s->pci_irq = cf->irq;
11962306a36Sopenharmony_ci	} else
12062306a36Sopenharmony_ci		*sp = 0;
12162306a36Sopenharmony_ci	return 0;
12262306a36Sopenharmony_ci}
12362306a36Sopenharmony_ci
12462306a36Sopenharmony_cistatic int
12562306a36Sopenharmony_ciomap_cf_set_socket(struct pcmcia_socket *sock, struct socket_state_t *s)
12662306a36Sopenharmony_ci{
12762306a36Sopenharmony_ci	/* REVISIT some non-OSK boards may support power switching */
12862306a36Sopenharmony_ci	switch (s->Vcc) {
12962306a36Sopenharmony_ci	case 0:
13062306a36Sopenharmony_ci	case 33:
13162306a36Sopenharmony_ci		break;
13262306a36Sopenharmony_ci	default:
13362306a36Sopenharmony_ci		return -EINVAL;
13462306a36Sopenharmony_ci	}
13562306a36Sopenharmony_ci
13662306a36Sopenharmony_ci	omap_readw(CF_CONTROL);
13762306a36Sopenharmony_ci	if (s->flags & SS_RESET)
13862306a36Sopenharmony_ci		omap_writew(CF_CONTROL_RESET, CF_CONTROL);
13962306a36Sopenharmony_ci	else
14062306a36Sopenharmony_ci		omap_writew(0, CF_CONTROL);
14162306a36Sopenharmony_ci
14262306a36Sopenharmony_ci	pr_debug("%s: Vcc %d, io_irq %d, flags %04x csc %04x\n",
14362306a36Sopenharmony_ci		driver_name, s->Vcc, s->io_irq, s->flags, s->csc_mask);
14462306a36Sopenharmony_ci
14562306a36Sopenharmony_ci	return 0;
14662306a36Sopenharmony_ci}
14762306a36Sopenharmony_ci
14862306a36Sopenharmony_cistatic int omap_cf_ss_suspend(struct pcmcia_socket *s)
14962306a36Sopenharmony_ci{
15062306a36Sopenharmony_ci	pr_debug("%s: %s\n", driver_name, __func__);
15162306a36Sopenharmony_ci	return omap_cf_set_socket(s, &dead_socket);
15262306a36Sopenharmony_ci}
15362306a36Sopenharmony_ci
15462306a36Sopenharmony_ci/* regions are 2K each:  mem, attrib, io (and reserved-for-ide) */
15562306a36Sopenharmony_ci
15662306a36Sopenharmony_cistatic int
15762306a36Sopenharmony_ciomap_cf_set_io_map(struct pcmcia_socket *s, struct pccard_io_map *io)
15862306a36Sopenharmony_ci{
15962306a36Sopenharmony_ci	struct omap_cf_socket	*cf;
16062306a36Sopenharmony_ci
16162306a36Sopenharmony_ci	cf = container_of(s, struct omap_cf_socket, socket);
16262306a36Sopenharmony_ci	io->flags &= MAP_ACTIVE|MAP_ATTRIB|MAP_16BIT;
16362306a36Sopenharmony_ci	io->start = cf->phys_cf + SZ_4K;
16462306a36Sopenharmony_ci	io->stop = io->start + SZ_2K - 1;
16562306a36Sopenharmony_ci	return 0;
16662306a36Sopenharmony_ci}
16762306a36Sopenharmony_ci
16862306a36Sopenharmony_cistatic int
16962306a36Sopenharmony_ciomap_cf_set_mem_map(struct pcmcia_socket *s, struct pccard_mem_map *map)
17062306a36Sopenharmony_ci{
17162306a36Sopenharmony_ci	struct omap_cf_socket	*cf;
17262306a36Sopenharmony_ci
17362306a36Sopenharmony_ci	if (map->card_start)
17462306a36Sopenharmony_ci		return -EINVAL;
17562306a36Sopenharmony_ci	cf = container_of(s, struct omap_cf_socket, socket);
17662306a36Sopenharmony_ci	map->static_start = cf->phys_cf;
17762306a36Sopenharmony_ci	map->flags &= MAP_ACTIVE|MAP_ATTRIB|MAP_16BIT;
17862306a36Sopenharmony_ci	if (map->flags & MAP_ATTRIB)
17962306a36Sopenharmony_ci		map->static_start += SZ_2K;
18062306a36Sopenharmony_ci	return 0;
18162306a36Sopenharmony_ci}
18262306a36Sopenharmony_ci
18362306a36Sopenharmony_cistatic struct pccard_operations omap_cf_ops = {
18462306a36Sopenharmony_ci	.init			= omap_cf_ss_init,
18562306a36Sopenharmony_ci	.suspend		= omap_cf_ss_suspend,
18662306a36Sopenharmony_ci	.get_status		= omap_cf_get_status,
18762306a36Sopenharmony_ci	.set_socket		= omap_cf_set_socket,
18862306a36Sopenharmony_ci	.set_io_map		= omap_cf_set_io_map,
18962306a36Sopenharmony_ci	.set_mem_map		= omap_cf_set_mem_map,
19062306a36Sopenharmony_ci};
19162306a36Sopenharmony_ci
19262306a36Sopenharmony_ci/*--------------------------------------------------------------------------*/
19362306a36Sopenharmony_ci
19462306a36Sopenharmony_ci/*
19562306a36Sopenharmony_ci * NOTE:  right now the only board-specific platform_data is
19662306a36Sopenharmony_ci * "what chipselect is used".  Boards could want more.
19762306a36Sopenharmony_ci */
19862306a36Sopenharmony_ci
19962306a36Sopenharmony_cistatic int __init omap_cf_probe(struct platform_device *pdev)
20062306a36Sopenharmony_ci{
20162306a36Sopenharmony_ci	unsigned		seg;
20262306a36Sopenharmony_ci	struct omap_cf_socket	*cf;
20362306a36Sopenharmony_ci	int			irq;
20462306a36Sopenharmony_ci	int			status;
20562306a36Sopenharmony_ci	struct resource		*res;
20662306a36Sopenharmony_ci	struct resource		iospace = DEFINE_RES_IO(SZ_64, SZ_4K);
20762306a36Sopenharmony_ci
20862306a36Sopenharmony_ci	seg = (int) pdev->dev.platform_data;
20962306a36Sopenharmony_ci	if (seg == 0 || seg > 3)
21062306a36Sopenharmony_ci		return -ENODEV;
21162306a36Sopenharmony_ci
21262306a36Sopenharmony_ci	/* either CFLASH.IREQ (INT_1610_CF) or some GPIO */
21362306a36Sopenharmony_ci	irq = platform_get_irq(pdev, 0);
21462306a36Sopenharmony_ci	if (irq < 0)
21562306a36Sopenharmony_ci		return -EINVAL;
21662306a36Sopenharmony_ci
21762306a36Sopenharmony_ci	res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
21862306a36Sopenharmony_ci
21962306a36Sopenharmony_ci	cf = kzalloc(sizeof *cf, GFP_KERNEL);
22062306a36Sopenharmony_ci	if (!cf)
22162306a36Sopenharmony_ci		return -ENOMEM;
22262306a36Sopenharmony_ci	timer_setup(&cf->timer, omap_cf_timer, 0);
22362306a36Sopenharmony_ci
22462306a36Sopenharmony_ci	cf->pdev = pdev;
22562306a36Sopenharmony_ci	platform_set_drvdata(pdev, cf);
22662306a36Sopenharmony_ci
22762306a36Sopenharmony_ci	/* this primarily just shuts up irq handling noise */
22862306a36Sopenharmony_ci	status = request_irq(irq, omap_cf_irq, IRQF_SHARED,
22962306a36Sopenharmony_ci			driver_name, cf);
23062306a36Sopenharmony_ci	if (status < 0)
23162306a36Sopenharmony_ci		goto fail0;
23262306a36Sopenharmony_ci	cf->irq = irq;
23362306a36Sopenharmony_ci	cf->socket.pci_irq = irq;
23462306a36Sopenharmony_ci	cf->phys_cf = res->start;
23562306a36Sopenharmony_ci
23662306a36Sopenharmony_ci	/* pcmcia layer only remaps "real" memory */
23762306a36Sopenharmony_ci	cf->socket.io_offset = iospace.start;
23862306a36Sopenharmony_ci	status = pci_remap_iospace(&iospace, cf->phys_cf + SZ_4K);
23962306a36Sopenharmony_ci	if (status) {
24062306a36Sopenharmony_ci		status = -ENOMEM;
24162306a36Sopenharmony_ci		goto fail1;
24262306a36Sopenharmony_ci	}
24362306a36Sopenharmony_ci
24462306a36Sopenharmony_ci	if (!request_mem_region(cf->phys_cf, SZ_8K, driver_name)) {
24562306a36Sopenharmony_ci		status = -ENXIO;
24662306a36Sopenharmony_ci		goto fail1;
24762306a36Sopenharmony_ci	}
24862306a36Sopenharmony_ci
24962306a36Sopenharmony_ci	/* NOTE:  CF conflicts with MMC1 */
25062306a36Sopenharmony_ci	omap_cfg_reg(W11_1610_CF_CD1);
25162306a36Sopenharmony_ci	omap_cfg_reg(P11_1610_CF_CD2);
25262306a36Sopenharmony_ci	omap_cfg_reg(R11_1610_CF_IOIS16);
25362306a36Sopenharmony_ci	omap_cfg_reg(V10_1610_CF_IREQ);
25462306a36Sopenharmony_ci	omap_cfg_reg(W10_1610_CF_RESET);
25562306a36Sopenharmony_ci
25662306a36Sopenharmony_ci	omap_writew(~(1 << seg), CF_CFG);
25762306a36Sopenharmony_ci
25862306a36Sopenharmony_ci	pr_info("%s: cs%d on irq %d\n", driver_name, seg, irq);
25962306a36Sopenharmony_ci
26062306a36Sopenharmony_ci	/* CF uses armxor_ck, which is "always" available */
26162306a36Sopenharmony_ci
26262306a36Sopenharmony_ci	pr_debug("%s: sts %04x cfg %04x control %04x %s\n", driver_name,
26362306a36Sopenharmony_ci		omap_readw(CF_STATUS), omap_readw(CF_CFG),
26462306a36Sopenharmony_ci		omap_readw(CF_CONTROL),
26562306a36Sopenharmony_ci		omap_cf_present() ? "present" : "(not present)");
26662306a36Sopenharmony_ci
26762306a36Sopenharmony_ci	cf->socket.owner = THIS_MODULE;
26862306a36Sopenharmony_ci	cf->socket.dev.parent = &pdev->dev;
26962306a36Sopenharmony_ci	cf->socket.ops = &omap_cf_ops;
27062306a36Sopenharmony_ci	cf->socket.resource_ops = &pccard_static_ops;
27162306a36Sopenharmony_ci	cf->socket.features = SS_CAP_PCCARD | SS_CAP_STATIC_MAP
27262306a36Sopenharmony_ci				| SS_CAP_MEM_ALIGN;
27362306a36Sopenharmony_ci	cf->socket.map_size = SZ_2K;
27462306a36Sopenharmony_ci	cf->socket.io[0].res = &cf->iomem;
27562306a36Sopenharmony_ci
27662306a36Sopenharmony_ci	status = pcmcia_register_socket(&cf->socket);
27762306a36Sopenharmony_ci	if (status < 0)
27862306a36Sopenharmony_ci		goto fail2;
27962306a36Sopenharmony_ci
28062306a36Sopenharmony_ci	cf->active = 1;
28162306a36Sopenharmony_ci	mod_timer(&cf->timer, jiffies + POLL_INTERVAL);
28262306a36Sopenharmony_ci	return 0;
28362306a36Sopenharmony_ci
28462306a36Sopenharmony_cifail2:
28562306a36Sopenharmony_ci	release_mem_region(cf->phys_cf, SZ_8K);
28662306a36Sopenharmony_cifail1:
28762306a36Sopenharmony_ci	free_irq(irq, cf);
28862306a36Sopenharmony_cifail0:
28962306a36Sopenharmony_ci	kfree(cf);
29062306a36Sopenharmony_ci	return status;
29162306a36Sopenharmony_ci}
29262306a36Sopenharmony_ci
29362306a36Sopenharmony_cistatic int __exit omap_cf_remove(struct platform_device *pdev)
29462306a36Sopenharmony_ci{
29562306a36Sopenharmony_ci	struct omap_cf_socket *cf = platform_get_drvdata(pdev);
29662306a36Sopenharmony_ci
29762306a36Sopenharmony_ci	cf->active = 0;
29862306a36Sopenharmony_ci	pcmcia_unregister_socket(&cf->socket);
29962306a36Sopenharmony_ci	timer_shutdown_sync(&cf->timer);
30062306a36Sopenharmony_ci	release_mem_region(cf->phys_cf, SZ_8K);
30162306a36Sopenharmony_ci	free_irq(cf->irq, cf);
30262306a36Sopenharmony_ci	kfree(cf);
30362306a36Sopenharmony_ci	return 0;
30462306a36Sopenharmony_ci}
30562306a36Sopenharmony_ci
30662306a36Sopenharmony_cistatic struct platform_driver omap_cf_driver = {
30762306a36Sopenharmony_ci	.driver = {
30862306a36Sopenharmony_ci		.name	= driver_name,
30962306a36Sopenharmony_ci	},
31062306a36Sopenharmony_ci	.remove		= __exit_p(omap_cf_remove),
31162306a36Sopenharmony_ci};
31262306a36Sopenharmony_ci
31362306a36Sopenharmony_cistatic int __init omap_cf_init(void)
31462306a36Sopenharmony_ci{
31562306a36Sopenharmony_ci	if (cpu_is_omap16xx())
31662306a36Sopenharmony_ci		return platform_driver_probe(&omap_cf_driver, omap_cf_probe);
31762306a36Sopenharmony_ci	return -ENODEV;
31862306a36Sopenharmony_ci}
31962306a36Sopenharmony_ci
32062306a36Sopenharmony_cistatic void __exit omap_cf_exit(void)
32162306a36Sopenharmony_ci{
32262306a36Sopenharmony_ci	if (cpu_is_omap16xx())
32362306a36Sopenharmony_ci		platform_driver_unregister(&omap_cf_driver);
32462306a36Sopenharmony_ci}
32562306a36Sopenharmony_ci
32662306a36Sopenharmony_cimodule_init(omap_cf_init);
32762306a36Sopenharmony_cimodule_exit(omap_cf_exit);
32862306a36Sopenharmony_ci
32962306a36Sopenharmony_ciMODULE_DESCRIPTION("OMAP CF Driver");
33062306a36Sopenharmony_ciMODULE_LICENSE("GPL");
33162306a36Sopenharmony_ciMODULE_ALIAS("platform:omap_cf");
332