162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-or-later
262306a36Sopenharmony_ci/*
362306a36Sopenharmony_ci * Copyright (C) 2007 PA Semi, Inc
462306a36Sopenharmony_ci *
562306a36Sopenharmony_ci * Maintained by: Olof Johansson <olof@lixom.net>
662306a36Sopenharmony_ci *
762306a36Sopenharmony_ci * Based on drivers/pcmcia/omap_cf.c
862306a36Sopenharmony_ci */
962306a36Sopenharmony_ci
1062306a36Sopenharmony_ci#include <linux/module.h>
1162306a36Sopenharmony_ci#include <linux/kernel.h>
1262306a36Sopenharmony_ci#include <linux/sched.h>
1362306a36Sopenharmony_ci#include <linux/platform_device.h>
1462306a36Sopenharmony_ci#include <linux/errno.h>
1562306a36Sopenharmony_ci#include <linux/init.h>
1662306a36Sopenharmony_ci#include <linux/delay.h>
1762306a36Sopenharmony_ci#include <linux/interrupt.h>
1862306a36Sopenharmony_ci#include <linux/mm.h>
1962306a36Sopenharmony_ci#include <linux/vmalloc.h>
2062306a36Sopenharmony_ci#include <linux/of_address.h>
2162306a36Sopenharmony_ci#include <linux/of_irq.h>
2262306a36Sopenharmony_ci#include <linux/of_platform.h>
2362306a36Sopenharmony_ci#include <linux/slab.h>
2462306a36Sopenharmony_ci
2562306a36Sopenharmony_ci#include <pcmcia/ss.h>
2662306a36Sopenharmony_ci
2762306a36Sopenharmony_cistatic const char driver_name[] = "electra-cf";
2862306a36Sopenharmony_ci
2962306a36Sopenharmony_cistruct electra_cf_socket {
3062306a36Sopenharmony_ci	struct pcmcia_socket	socket;
3162306a36Sopenharmony_ci
3262306a36Sopenharmony_ci	struct timer_list	timer;
3362306a36Sopenharmony_ci	unsigned		present:1;
3462306a36Sopenharmony_ci	unsigned		active:1;
3562306a36Sopenharmony_ci
3662306a36Sopenharmony_ci	struct platform_device	*ofdev;
3762306a36Sopenharmony_ci	unsigned long		mem_phys;
3862306a36Sopenharmony_ci	void __iomem		*mem_base;
3962306a36Sopenharmony_ci	unsigned long		mem_size;
4062306a36Sopenharmony_ci	void __iomem		*io_virt;
4162306a36Sopenharmony_ci	unsigned int		io_base;
4262306a36Sopenharmony_ci	unsigned int		io_size;
4362306a36Sopenharmony_ci	u_int			irq;
4462306a36Sopenharmony_ci	struct resource		iomem;
4562306a36Sopenharmony_ci	void __iomem		*gpio_base;
4662306a36Sopenharmony_ci	int			gpio_detect;
4762306a36Sopenharmony_ci	int			gpio_vsense;
4862306a36Sopenharmony_ci	int			gpio_3v;
4962306a36Sopenharmony_ci	int			gpio_5v;
5062306a36Sopenharmony_ci};
5162306a36Sopenharmony_ci
5262306a36Sopenharmony_ci#define	POLL_INTERVAL		(2 * HZ)
5362306a36Sopenharmony_ci
5462306a36Sopenharmony_ci
5562306a36Sopenharmony_cistatic int electra_cf_present(struct electra_cf_socket *cf)
5662306a36Sopenharmony_ci{
5762306a36Sopenharmony_ci	unsigned int gpio;
5862306a36Sopenharmony_ci
5962306a36Sopenharmony_ci	gpio = in_le32(cf->gpio_base+0x40);
6062306a36Sopenharmony_ci	return !(gpio & (1 << cf->gpio_detect));
6162306a36Sopenharmony_ci}
6262306a36Sopenharmony_ci
6362306a36Sopenharmony_cistatic int electra_cf_ss_init(struct pcmcia_socket *s)
6462306a36Sopenharmony_ci{
6562306a36Sopenharmony_ci	return 0;
6662306a36Sopenharmony_ci}
6762306a36Sopenharmony_ci
6862306a36Sopenharmony_ci/* the timer is primarily to kick this socket's pccardd */
6962306a36Sopenharmony_cistatic void electra_cf_timer(struct timer_list *t)
7062306a36Sopenharmony_ci{
7162306a36Sopenharmony_ci	struct electra_cf_socket *cf = from_timer(cf, t, timer);
7262306a36Sopenharmony_ci	int present = electra_cf_present(cf);
7362306a36Sopenharmony_ci
7462306a36Sopenharmony_ci	if (present != cf->present) {
7562306a36Sopenharmony_ci		cf->present = present;
7662306a36Sopenharmony_ci		pcmcia_parse_events(&cf->socket, SS_DETECT);
7762306a36Sopenharmony_ci	}
7862306a36Sopenharmony_ci
7962306a36Sopenharmony_ci	if (cf->active)
8062306a36Sopenharmony_ci		mod_timer(&cf->timer, jiffies + POLL_INTERVAL);
8162306a36Sopenharmony_ci}
8262306a36Sopenharmony_ci
8362306a36Sopenharmony_cistatic irqreturn_t electra_cf_irq(int irq, void *_cf)
8462306a36Sopenharmony_ci{
8562306a36Sopenharmony_ci	struct electra_cf_socket *cf = _cf;
8662306a36Sopenharmony_ci
8762306a36Sopenharmony_ci	electra_cf_timer(&cf->timer);
8862306a36Sopenharmony_ci	return IRQ_HANDLED;
8962306a36Sopenharmony_ci}
9062306a36Sopenharmony_ci
9162306a36Sopenharmony_cistatic int electra_cf_get_status(struct pcmcia_socket *s, u_int *sp)
9262306a36Sopenharmony_ci{
9362306a36Sopenharmony_ci	struct electra_cf_socket *cf;
9462306a36Sopenharmony_ci
9562306a36Sopenharmony_ci	if (!sp)
9662306a36Sopenharmony_ci		return -EINVAL;
9762306a36Sopenharmony_ci
9862306a36Sopenharmony_ci	cf = container_of(s, struct electra_cf_socket, socket);
9962306a36Sopenharmony_ci
10062306a36Sopenharmony_ci	/* NOTE CF is always 3VCARD */
10162306a36Sopenharmony_ci	if (electra_cf_present(cf)) {
10262306a36Sopenharmony_ci		*sp = SS_READY | SS_DETECT | SS_POWERON | SS_3VCARD;
10362306a36Sopenharmony_ci
10462306a36Sopenharmony_ci		s->pci_irq = cf->irq;
10562306a36Sopenharmony_ci	} else
10662306a36Sopenharmony_ci		*sp = 0;
10762306a36Sopenharmony_ci	return 0;
10862306a36Sopenharmony_ci}
10962306a36Sopenharmony_ci
11062306a36Sopenharmony_cistatic int electra_cf_set_socket(struct pcmcia_socket *sock,
11162306a36Sopenharmony_ci				 struct socket_state_t *s)
11262306a36Sopenharmony_ci{
11362306a36Sopenharmony_ci	unsigned int gpio;
11462306a36Sopenharmony_ci	unsigned int vcc;
11562306a36Sopenharmony_ci	struct electra_cf_socket *cf;
11662306a36Sopenharmony_ci
11762306a36Sopenharmony_ci	cf = container_of(sock, struct electra_cf_socket, socket);
11862306a36Sopenharmony_ci
11962306a36Sopenharmony_ci	/* "reset" means no power in our case */
12062306a36Sopenharmony_ci	vcc = (s->flags & SS_RESET) ? 0 : s->Vcc;
12162306a36Sopenharmony_ci
12262306a36Sopenharmony_ci	switch (vcc) {
12362306a36Sopenharmony_ci	case 0:
12462306a36Sopenharmony_ci		gpio = 0;
12562306a36Sopenharmony_ci		break;
12662306a36Sopenharmony_ci	case 33:
12762306a36Sopenharmony_ci		gpio = (1 << cf->gpio_3v);
12862306a36Sopenharmony_ci		break;
12962306a36Sopenharmony_ci	case 5:
13062306a36Sopenharmony_ci		gpio = (1 << cf->gpio_5v);
13162306a36Sopenharmony_ci		break;
13262306a36Sopenharmony_ci	default:
13362306a36Sopenharmony_ci		return -EINVAL;
13462306a36Sopenharmony_ci	}
13562306a36Sopenharmony_ci
13662306a36Sopenharmony_ci	gpio |= 1 << (cf->gpio_3v + 16); /* enwr */
13762306a36Sopenharmony_ci	gpio |= 1 << (cf->gpio_5v + 16); /* enwr */
13862306a36Sopenharmony_ci	out_le32(cf->gpio_base+0x90, gpio);
13962306a36Sopenharmony_ci
14062306a36Sopenharmony_ci	pr_debug("%s: Vcc %d, io_irq %d, flags %04x csc %04x\n",
14162306a36Sopenharmony_ci		driver_name, s->Vcc, s->io_irq, s->flags, s->csc_mask);
14262306a36Sopenharmony_ci
14362306a36Sopenharmony_ci	return 0;
14462306a36Sopenharmony_ci}
14562306a36Sopenharmony_ci
14662306a36Sopenharmony_cistatic int electra_cf_set_io_map(struct pcmcia_socket *s,
14762306a36Sopenharmony_ci				 struct pccard_io_map *io)
14862306a36Sopenharmony_ci{
14962306a36Sopenharmony_ci	return 0;
15062306a36Sopenharmony_ci}
15162306a36Sopenharmony_ci
15262306a36Sopenharmony_cistatic int electra_cf_set_mem_map(struct pcmcia_socket *s,
15362306a36Sopenharmony_ci				  struct pccard_mem_map *map)
15462306a36Sopenharmony_ci{
15562306a36Sopenharmony_ci	struct electra_cf_socket *cf;
15662306a36Sopenharmony_ci
15762306a36Sopenharmony_ci	if (map->card_start)
15862306a36Sopenharmony_ci		return -EINVAL;
15962306a36Sopenharmony_ci	cf = container_of(s, struct electra_cf_socket, socket);
16062306a36Sopenharmony_ci	map->static_start = cf->mem_phys;
16162306a36Sopenharmony_ci	map->flags &= MAP_ACTIVE|MAP_ATTRIB;
16262306a36Sopenharmony_ci	if (!(map->flags & MAP_ATTRIB))
16362306a36Sopenharmony_ci		map->static_start += 0x800;
16462306a36Sopenharmony_ci	return 0;
16562306a36Sopenharmony_ci}
16662306a36Sopenharmony_ci
16762306a36Sopenharmony_cistatic struct pccard_operations electra_cf_ops = {
16862306a36Sopenharmony_ci	.init			= electra_cf_ss_init,
16962306a36Sopenharmony_ci	.get_status		= electra_cf_get_status,
17062306a36Sopenharmony_ci	.set_socket		= electra_cf_set_socket,
17162306a36Sopenharmony_ci	.set_io_map		= electra_cf_set_io_map,
17262306a36Sopenharmony_ci	.set_mem_map		= electra_cf_set_mem_map,
17362306a36Sopenharmony_ci};
17462306a36Sopenharmony_ci
17562306a36Sopenharmony_cistatic int electra_cf_probe(struct platform_device *ofdev)
17662306a36Sopenharmony_ci{
17762306a36Sopenharmony_ci	struct device *device = &ofdev->dev;
17862306a36Sopenharmony_ci	struct device_node *np = ofdev->dev.of_node;
17962306a36Sopenharmony_ci	struct electra_cf_socket   *cf;
18062306a36Sopenharmony_ci	struct resource mem, io;
18162306a36Sopenharmony_ci	int status = -ENOMEM;
18262306a36Sopenharmony_ci	const unsigned int *prop;
18362306a36Sopenharmony_ci	int err;
18462306a36Sopenharmony_ci
18562306a36Sopenharmony_ci	err = of_address_to_resource(np, 0, &mem);
18662306a36Sopenharmony_ci	if (err)
18762306a36Sopenharmony_ci		return -EINVAL;
18862306a36Sopenharmony_ci
18962306a36Sopenharmony_ci	err = of_address_to_resource(np, 1, &io);
19062306a36Sopenharmony_ci	if (err)
19162306a36Sopenharmony_ci		return -EINVAL;
19262306a36Sopenharmony_ci
19362306a36Sopenharmony_ci	cf = kzalloc(sizeof(*cf), GFP_KERNEL);
19462306a36Sopenharmony_ci	if (!cf)
19562306a36Sopenharmony_ci		return -ENOMEM;
19662306a36Sopenharmony_ci
19762306a36Sopenharmony_ci	timer_setup(&cf->timer, electra_cf_timer, 0);
19862306a36Sopenharmony_ci	cf->irq = 0;
19962306a36Sopenharmony_ci
20062306a36Sopenharmony_ci	cf->ofdev = ofdev;
20162306a36Sopenharmony_ci	cf->mem_phys = mem.start;
20262306a36Sopenharmony_ci	cf->mem_size = PAGE_ALIGN(resource_size(&mem));
20362306a36Sopenharmony_ci	cf->mem_base = ioremap(cf->mem_phys, cf->mem_size);
20462306a36Sopenharmony_ci	if (!cf->mem_base)
20562306a36Sopenharmony_ci		goto out_free_cf;
20662306a36Sopenharmony_ci	cf->io_size = PAGE_ALIGN(resource_size(&io));
20762306a36Sopenharmony_ci	cf->io_virt = ioremap_phb(io.start, cf->io_size);
20862306a36Sopenharmony_ci	if (!cf->io_virt)
20962306a36Sopenharmony_ci		goto out_unmap_mem;
21062306a36Sopenharmony_ci
21162306a36Sopenharmony_ci	cf->gpio_base = ioremap(0xfc103000, 0x1000);
21262306a36Sopenharmony_ci	if (!cf->gpio_base)
21362306a36Sopenharmony_ci		goto out_unmap_virt;
21462306a36Sopenharmony_ci	dev_set_drvdata(device, cf);
21562306a36Sopenharmony_ci
21662306a36Sopenharmony_ci	cf->io_base = (unsigned long)cf->io_virt - VMALLOC_END;
21762306a36Sopenharmony_ci	cf->iomem.start = (unsigned long)cf->mem_base;
21862306a36Sopenharmony_ci	cf->iomem.end = (unsigned long)cf->mem_base + (mem.end - mem.start);
21962306a36Sopenharmony_ci	cf->iomem.flags = IORESOURCE_MEM;
22062306a36Sopenharmony_ci
22162306a36Sopenharmony_ci	cf->irq = irq_of_parse_and_map(np, 0);
22262306a36Sopenharmony_ci
22362306a36Sopenharmony_ci	status = request_irq(cf->irq, electra_cf_irq, IRQF_SHARED,
22462306a36Sopenharmony_ci			     driver_name, cf);
22562306a36Sopenharmony_ci	if (status < 0) {
22662306a36Sopenharmony_ci		dev_err(device, "request_irq failed\n");
22762306a36Sopenharmony_ci		goto fail1;
22862306a36Sopenharmony_ci	}
22962306a36Sopenharmony_ci
23062306a36Sopenharmony_ci	cf->socket.pci_irq = cf->irq;
23162306a36Sopenharmony_ci
23262306a36Sopenharmony_ci	status = -EINVAL;
23362306a36Sopenharmony_ci
23462306a36Sopenharmony_ci	prop = of_get_property(np, "card-detect-gpio", NULL);
23562306a36Sopenharmony_ci	if (!prop)
23662306a36Sopenharmony_ci		goto fail1;
23762306a36Sopenharmony_ci	cf->gpio_detect = *prop;
23862306a36Sopenharmony_ci
23962306a36Sopenharmony_ci	prop = of_get_property(np, "card-vsense-gpio", NULL);
24062306a36Sopenharmony_ci	if (!prop)
24162306a36Sopenharmony_ci		goto fail1;
24262306a36Sopenharmony_ci	cf->gpio_vsense = *prop;
24362306a36Sopenharmony_ci
24462306a36Sopenharmony_ci	prop = of_get_property(np, "card-3v-gpio", NULL);
24562306a36Sopenharmony_ci	if (!prop)
24662306a36Sopenharmony_ci		goto fail1;
24762306a36Sopenharmony_ci	cf->gpio_3v = *prop;
24862306a36Sopenharmony_ci
24962306a36Sopenharmony_ci	prop = of_get_property(np, "card-5v-gpio", NULL);
25062306a36Sopenharmony_ci	if (!prop)
25162306a36Sopenharmony_ci		goto fail1;
25262306a36Sopenharmony_ci	cf->gpio_5v = *prop;
25362306a36Sopenharmony_ci
25462306a36Sopenharmony_ci	cf->socket.io_offset = cf->io_base;
25562306a36Sopenharmony_ci
25662306a36Sopenharmony_ci	/* reserve chip-select regions */
25762306a36Sopenharmony_ci	if (!request_mem_region(cf->mem_phys, cf->mem_size, driver_name)) {
25862306a36Sopenharmony_ci		status = -ENXIO;
25962306a36Sopenharmony_ci		dev_err(device, "Can't claim memory region\n");
26062306a36Sopenharmony_ci		goto fail1;
26162306a36Sopenharmony_ci	}
26262306a36Sopenharmony_ci
26362306a36Sopenharmony_ci	if (!request_region(cf->io_base, cf->io_size, driver_name)) {
26462306a36Sopenharmony_ci		status = -ENXIO;
26562306a36Sopenharmony_ci		dev_err(device, "Can't claim I/O region\n");
26662306a36Sopenharmony_ci		goto fail2;
26762306a36Sopenharmony_ci	}
26862306a36Sopenharmony_ci
26962306a36Sopenharmony_ci	cf->socket.owner = THIS_MODULE;
27062306a36Sopenharmony_ci	cf->socket.dev.parent = &ofdev->dev;
27162306a36Sopenharmony_ci	cf->socket.ops = &electra_cf_ops;
27262306a36Sopenharmony_ci	cf->socket.resource_ops = &pccard_static_ops;
27362306a36Sopenharmony_ci	cf->socket.features = SS_CAP_PCCARD | SS_CAP_STATIC_MAP |
27462306a36Sopenharmony_ci				SS_CAP_MEM_ALIGN;
27562306a36Sopenharmony_ci	cf->socket.map_size = 0x800;
27662306a36Sopenharmony_ci
27762306a36Sopenharmony_ci	status = pcmcia_register_socket(&cf->socket);
27862306a36Sopenharmony_ci	if (status < 0) {
27962306a36Sopenharmony_ci		dev_err(device, "pcmcia_register_socket failed\n");
28062306a36Sopenharmony_ci		goto fail3;
28162306a36Sopenharmony_ci	}
28262306a36Sopenharmony_ci
28362306a36Sopenharmony_ci	dev_info(device, "at mem 0x%lx io 0x%llx irq %d\n",
28462306a36Sopenharmony_ci		 cf->mem_phys, io.start, cf->irq);
28562306a36Sopenharmony_ci
28662306a36Sopenharmony_ci	cf->active = 1;
28762306a36Sopenharmony_ci	electra_cf_timer(&cf->timer);
28862306a36Sopenharmony_ci	return 0;
28962306a36Sopenharmony_ci
29062306a36Sopenharmony_cifail3:
29162306a36Sopenharmony_ci	release_region(cf->io_base, cf->io_size);
29262306a36Sopenharmony_cifail2:
29362306a36Sopenharmony_ci	release_mem_region(cf->mem_phys, cf->mem_size);
29462306a36Sopenharmony_cifail1:
29562306a36Sopenharmony_ci	if (cf->irq)
29662306a36Sopenharmony_ci		free_irq(cf->irq, cf);
29762306a36Sopenharmony_ci
29862306a36Sopenharmony_ci	iounmap(cf->gpio_base);
29962306a36Sopenharmony_ciout_unmap_virt:
30062306a36Sopenharmony_ci	device_init_wakeup(&ofdev->dev, 0);
30162306a36Sopenharmony_ci	iounmap(cf->io_virt);
30262306a36Sopenharmony_ciout_unmap_mem:
30362306a36Sopenharmony_ci	iounmap(cf->mem_base);
30462306a36Sopenharmony_ciout_free_cf:
30562306a36Sopenharmony_ci	kfree(cf);
30662306a36Sopenharmony_ci	return status;
30762306a36Sopenharmony_ci
30862306a36Sopenharmony_ci}
30962306a36Sopenharmony_ci
31062306a36Sopenharmony_cistatic int electra_cf_remove(struct platform_device *ofdev)
31162306a36Sopenharmony_ci{
31262306a36Sopenharmony_ci	struct device *device = &ofdev->dev;
31362306a36Sopenharmony_ci	struct electra_cf_socket *cf;
31462306a36Sopenharmony_ci
31562306a36Sopenharmony_ci	cf = dev_get_drvdata(device);
31662306a36Sopenharmony_ci
31762306a36Sopenharmony_ci	cf->active = 0;
31862306a36Sopenharmony_ci	pcmcia_unregister_socket(&cf->socket);
31962306a36Sopenharmony_ci	free_irq(cf->irq, cf);
32062306a36Sopenharmony_ci	timer_shutdown_sync(&cf->timer);
32162306a36Sopenharmony_ci
32262306a36Sopenharmony_ci	iounmap(cf->io_virt);
32362306a36Sopenharmony_ci	iounmap(cf->mem_base);
32462306a36Sopenharmony_ci	iounmap(cf->gpio_base);
32562306a36Sopenharmony_ci	release_mem_region(cf->mem_phys, cf->mem_size);
32662306a36Sopenharmony_ci	release_region(cf->io_base, cf->io_size);
32762306a36Sopenharmony_ci
32862306a36Sopenharmony_ci	kfree(cf);
32962306a36Sopenharmony_ci
33062306a36Sopenharmony_ci	return 0;
33162306a36Sopenharmony_ci}
33262306a36Sopenharmony_ci
33362306a36Sopenharmony_cistatic const struct of_device_id electra_cf_match[] = {
33462306a36Sopenharmony_ci	{
33562306a36Sopenharmony_ci		.compatible   = "electra-cf",
33662306a36Sopenharmony_ci	},
33762306a36Sopenharmony_ci	{},
33862306a36Sopenharmony_ci};
33962306a36Sopenharmony_ciMODULE_DEVICE_TABLE(of, electra_cf_match);
34062306a36Sopenharmony_ci
34162306a36Sopenharmony_cistatic struct platform_driver electra_cf_driver = {
34262306a36Sopenharmony_ci	.driver = {
34362306a36Sopenharmony_ci		.name = driver_name,
34462306a36Sopenharmony_ci		.of_match_table = electra_cf_match,
34562306a36Sopenharmony_ci	},
34662306a36Sopenharmony_ci	.probe	  = electra_cf_probe,
34762306a36Sopenharmony_ci	.remove   = electra_cf_remove,
34862306a36Sopenharmony_ci};
34962306a36Sopenharmony_ci
35062306a36Sopenharmony_cimodule_platform_driver(electra_cf_driver);
35162306a36Sopenharmony_ci
35262306a36Sopenharmony_ciMODULE_LICENSE("GPL");
35362306a36Sopenharmony_ciMODULE_AUTHOR("Olof Johansson <olof@lixom.net>");
35462306a36Sopenharmony_ciMODULE_DESCRIPTION("PA Semi Electra CF driver");
355