[PATCH 1/1] pcmcia: gayle: initial support

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

 



Signed-off-by: Paolo Pisati <p.pisati@xxxxxxxxx>
---
 arch/m68k/amiga/pcmcia.c          |   5 +
 arch/m68k/amiga/platform.c        |  29 +++
 arch/m68k/include/asm/amipcmcia.h |  11 ++
 drivers/pcmcia/Kconfig            |   8 +
 drivers/pcmcia/Makefile           |   1 +
 drivers/pcmcia/gayle.c            | 307 ++++++++++++++++++++++++++++++
 6 files changed, 361 insertions(+)
 create mode 100644 drivers/pcmcia/gayle.c

diff --git a/arch/m68k/amiga/pcmcia.c b/arch/m68k/amiga/pcmcia.c
index 7106f0c3639b..4bdd8ca1a481 100644
--- a/arch/m68k/amiga/pcmcia.c
+++ b/arch/m68k/amiga/pcmcia.c
@@ -66,6 +66,11 @@ int pcmcia_copy_tuple(unsigned char tuple_id, void *tuple, int max_len)
 }
 EXPORT_SYMBOL(pcmcia_copy_tuple);
 
+unsigned char pcmcia_get_voltage(void) {
+	return gayle.config & 0x03;
+}
+EXPORT_SYMBOL(pcmcia_get_voltage);
+
 void pcmcia_program_voltage(int voltage)
 {
 	unsigned char v;
diff --git a/arch/m68k/amiga/platform.c b/arch/m68k/amiga/platform.c
index d34029d7b058..2dd6c788e5a0 100644
--- a/arch/m68k/amiga/platform.c
+++ b/arch/m68k/amiga/platform.c
@@ -110,6 +110,26 @@ static const struct gayle_ide_platform_data a1200_ide_pdata __initconst = {
 	.explicit_ack	= 1,
 };
 
+static const struct resource a1200_pcmcia_resource[] __initconst = {
+	[0] = {
+		.name	= "Gayle memory",
+		.start	= GAYLE_RAM,
+		.end	= GAYLE_RAM+GAYLE_RAMSIZE-1,
+		.flags	= IORESOURCE_MEM,
+	},
+	[1] = {
+		.name	= "Gayle attribute",
+		.start	= GAYLE_ATTRIBUTE,
+		.end	= GAYLE_ATTRIBUTE+GAYLE_ATTRIBUTESIZE-1,
+		.flags	= IORESOURCE_MEM,
+	},
+	[2] = {
+		.name	= "Gayle I/O",
+		.start	= GAYLE_IO,
+		.end	= GAYLE_IO+(2*GAYLE_IOSIZE)-1,
+		.flags	= IORESOURCE_MEM,
+	}
+};
 
 static const struct resource a4000_ide_resource __initconst = {
 	.start	= 0xdd2000,
@@ -190,6 +210,15 @@ static int __init amiga_init_devices(void)
 						 sizeof(a1200_ide_pdata));
 		if (error)
 			return error;
+
+	}
+
+	if (AMIGAHW_PRESENT(PCMCIA)) {
+		pdev = platform_device_register_simple("amiga-gayle-pcmcia", -1,
+						       a1200_pcmcia_resource,
+						       ARRAY_SIZE(a1200_pcmcia_resource));
+		if (IS_ERR(pdev))
+			return PTR_ERR(pdev);
 	}
 
 	if (AMIGAHW_PRESENT(A4000_IDE)) {
diff --git a/arch/m68k/include/asm/amipcmcia.h b/arch/m68k/include/asm/amipcmcia.h
index 6f1ec1887d82..281345bea41e 100644
--- a/arch/m68k/include/asm/amipcmcia.h
+++ b/arch/m68k/include/asm/amipcmcia.h
@@ -19,6 +19,7 @@
 
 void pcmcia_reset(void);
 int pcmcia_copy_tuple(unsigned char tuple_id, void *tuple, int max_len);
+unsigned char pcmcia_get_voltage(void);
 void pcmcia_program_voltage(int voltage);
 void pcmcia_access_speed(int speed);
 void pcmcia_write_enable(void);
@@ -51,6 +52,16 @@ static inline void pcmcia_disable_irq(void)
 
 #define PCMCIA_INSERTED	(gayle.cardstatus & GAYLE_CS_CCDET)
 
+static inline void pcmcia_disable_ccdet_irq(void)
+{
+	gayle.inten &= ~GAYLE_IRQ_CCDET;
+}
+
+static inline u_char pcmcia_wena(void)
+{
+	return (gayle.cardstatus & GAYLE_CS_WR);
+}
+
 /* valid voltages for pcmcia_ProgramVoltage */
 
 #define PCMCIA_0V	0
diff --git a/drivers/pcmcia/Kconfig b/drivers/pcmcia/Kconfig
index dddb235dd020..98883bafc95e 100644
--- a/drivers/pcmcia/Kconfig
+++ b/drivers/pcmcia/Kconfig
@@ -253,4 +253,12 @@ config PCCARD_NONSTATIC
 config PCCARD_IODYN
 	bool
 
+config PCMCIA_GAYLE
+	tristate "Amiga Gayle bridge support"
+	depends on PCMCIA && AMIGA && AMIGA_PCMCIA && !APNE
+	help
+	  Say Y here to include support for PCMCIA host bridges that
+	  are register compatible with Commodore's Gayle. This is found on
+	  Amiga systems. If unsure, say N.
+
 endif	# PCCARD
diff --git a/drivers/pcmcia/Makefile b/drivers/pcmcia/Makefile
index 31e0e34011bb..b89bcde972f9 100644
--- a/drivers/pcmcia/Makefile
+++ b/drivers/pcmcia/Makefile
@@ -35,6 +35,7 @@ obj-$(CONFIG_OMAP_CF)				+= omap_cf.o
 obj-$(CONFIG_ELECTRA_CF)			+= electra_cf.o
 obj-$(CONFIG_PCMCIA_ALCHEMY_DEVBOARD)		+= db1xxx_ss.o
 obj-$(CONFIG_PCMCIA_MAX1600)			+= max1600.o
+obj-$(CONFIG_PCMCIA_GAYLE)			+= gayle.o
 
 sa1111_cs-y					+= sa1111_generic.o
 sa1111_cs-$(CONFIG_ASSABET_NEPONSET)		+= sa1111_neponset.o
diff --git a/drivers/pcmcia/gayle.c b/drivers/pcmcia/gayle.c
new file mode 100644
index 000000000000..c951fcc4f9f7
--- /dev/null
+++ b/drivers/pcmcia/gayle.c
@@ -0,0 +1,307 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Commodore's Gayle PC Card controller driver
+ *
+ * Copyright (c) 2024 Paolo Pisati <p.pisati@xxxxxxxxx>
+ */
+
+#include <linux/delay.h>
+#include <linux/interrupt.h>
+#include <linux/platform_device.h>
+#include <linux/slab.h>
+
+#include <asm/amigaints.h>
+#include <asm/amigayle.h>
+#include <asm/amipcmcia.h>
+
+#include <pcmcia/ss.h>
+
+/* in case of emergency, break the glass */
+/* #define DEBUG */
+
+struct gayle_pcmcia_sock {
+	struct pcmcia_socket	socket;
+
+	phys_addr_t	phys_io;
+	void		*virt_io;
+	phys_addr_t	phys_attr;
+	phys_addr_t	phys_mem;
+
+	int	card_irq;
+};
+
+#define to_gayle_socket(x) container_of(x, struct gayle_pcmcia_sock, socket)
+
+static int gayle_sock_set_socket(struct pcmcia_socket *skt,
+				 struct socket_state_t *state)
+{
+	struct gayle_pcmcia_sock *sock = to_gayle_socket(skt);
+
+	dev_dbg(&skt->dev, "%s flags 0x%x csc_mask 0x%x io_irq 0x%x "
+		"Vcc %d Vpp %d\n",
+		__func__, state->flags, state->csc_mask, state->io_irq,
+		state->Vcc, state->Vpp);
+
+	if (state->Vpp && (state->Vcc != state->Vpp)) {
+		pr_err("pcmcia: unsupported Vpp %d\n", state->Vpp);
+		return -EINVAL;
+	}
+
+	if (state->flags & SS_RESET) {
+		pcmcia_reset();
+		msleep(500); /* give card time to inizialize */
+		pr_err("%s cardstatus: 0x%x\n", __func__, gayle.cardstatus);
+		pr_err("%s inten: 0x%x\n", __func__, gayle.inten);
+	}
+
+	/*
+	 * XXX shouldn't we apply power only when state->flags & SS_OUTPUT_ENA?
+	 * but if so, what's the point of setting a Vcc without powering the
+	 * socket on?
+	 */
+	switch (state->Vcc) {
+	case 0:
+		pcmcia_program_voltage(PCMCIA_0V);
+		break;
+	case 50:
+		pcmcia_program_voltage(PCMCIA_5V);
+		break;
+	default:
+		pr_err("pcmcia: unsupported Vcc %d\n", state->Vcc);
+	}
+
+	return 0;
+}
+
+static int gayle_sock_get_status(struct pcmcia_socket *skt,
+				  unsigned int *value)
+{
+	unsigned int status;
+
+	status = PCMCIA_INSERTED ? SS_DETECT : 0;
+
+	switch(pcmcia_get_voltage()) {
+		case GAYLE_CFG_5V:
+		case GAYLE_CFG_12V:
+			status |= SS_POWERON;	/* power is applied to the card */
+			break;
+		case GAYLE_CFG_0V:
+			break;
+		default:
+			status |= SS_XVCARD;	/* treated as unsupported in core */
+	}
+
+	status |= pcmcia_wena() ? 0 : SS_WRPROT;	/* card is write protected */
+
+	if (status & (SS_DETECT | SS_POWERON))
+		status |= SS_READY;
+
+	dev_dbg(&skt->dev,"%s status: 0x%x\n", __func__, status);
+	*value = status;
+
+	return 0;
+}
+
+static int gayle_sock_init(struct pcmcia_socket *skt)
+{
+	dev_dbg(&skt->dev, "%s::%d\n", __func__, __LINE__);
+	return 0;
+}
+
+static int gayle_sock_suspend(struct pcmcia_socket *skt)
+{
+	dev_dbg(&skt->dev, "%s::%d\n", __func__, __LINE__);
+	return 0;
+}
+
+static int gayle_sock_set_io_map(struct pcmcia_socket *skt,
+				    struct pccard_io_map *map)
+{
+	struct gayle_pcmcia_sock *sock = to_gayle_socket(skt);
+	dev_dbg(&skt->dev, "%s::%d\n", __func__, __LINE__);
+
+	map->start = sock->phys_io;
+	map->stop = map->start + GAYLE_IOSIZE;
+
+	return 0;
+}
+
+static int gayle_sock_set_mem_map(struct pcmcia_socket *skt,
+				     struct pccard_mem_map *map)
+{
+	struct gayle_pcmcia_sock *sock = to_gayle_socket(skt);
+	dev_dbg(&skt->dev, "%s::%d %s card_start: 0x%x\n", __func__, __LINE__,
+		(map->flags & MAP_ATTRIB) ? "ATTR" : "MEM", map->card_start);
+
+	if (map->flags & MAP_ATTRIB)
+		map->static_start = sock->phys_attr + map->card_start;
+	else
+		map->static_start = sock->phys_mem + map->card_start;
+
+	return 0;
+}
+
+static struct pccard_operations gayle_pcmcia_operations = {
+	.init			= gayle_sock_init,
+	.suspend		= gayle_sock_suspend,
+	.get_status		= gayle_sock_get_status,
+	.set_socket		= gayle_sock_set_socket,
+	.set_io_map		= gayle_sock_set_io_map,
+	.set_mem_map		= gayle_sock_set_mem_map,
+};
+
+struct gayle_pcmcia_sock *sock;
+
+static irqreturn_t gayle_stschg_irq(int irq, void *data)
+{
+	unsigned char pcmcia_intreq;
+	struct gayle_pcmcia_sock *sock = data;
+
+	pcmcia_intreq = pcmcia_get_intreq();
+	if (!(pcmcia_intreq & (GAYLE_IRQ_SC | GAYLE_IRQ_DA | GAYLE_IRQ_WR |
+			       GAYLE_IRQ_IRQ)))
+		return IRQ_NONE;
+
+	pr_debug("%s::%d intreq: 0x%x\n", __func__, __LINE__, pcmcia_intreq);
+	pcmcia_ack_int(pcmcia_get_intreq());
+	pr_debug("%s::%d intreq: 0x%x\n", __func__, __LINE__, pcmcia_get_intreq());
+	pcmcia_parse_events(&sock->socket, SS_STSCHG);
+	pr_debug("%s::%d intreq: 0x%x\n", __func__, __LINE__, pcmcia_get_intreq());
+
+	return IRQ_HANDLED;
+}
+
+static irqreturn_t gayle_ccdet_irq(int irq, void *data)
+{
+	unsigned char pcmcia_intreq;
+	struct gayle_pcmcia_sock *sock = data;
+
+	pcmcia_intreq = pcmcia_get_intreq();
+	if (!(pcmcia_intreq & GAYLE_IRQ_CCDET))
+		return IRQ_NONE;
+
+	pr_debug("%s::%d intreq: 0x%x\n", __func__, __LINE__, pcmcia_intreq);
+	gayle.intreq = GAYLE_IRQ_IDE | GAYLE_IRQ_SC | GAYLE_IRQ_DA |
+		GAYLE_IRQ_WR | GAYLE_IRQ_IRQ;
+	pr_debug("%s::%d intreq: 0x%x\n", __func__, __LINE__, pcmcia_get_intreq());
+	pcmcia_parse_events(&sock->socket, SS_DETECT);
+	pr_debug("%s::%d intreq: 0x%x\n", __func__, __LINE__, pcmcia_get_intreq());
+
+	return IRQ_HANDLED;
+}
+
+
+static int gayle_pcmcia_init(struct platform_device *pdev) {
+	struct resource *r;
+	int ret;
+
+	dev_dbg(&pdev->dev, "%s::%d\n", __func__, __LINE__);
+
+	sock = kzalloc(sizeof(struct gayle_pcmcia_sock), GFP_KERNEL);
+	if (!sock)
+		return -ENOMEM;
+
+	/* card: irq assigned to the card itself. */
+	sock->card_irq = IRQ_AMIGA_PORTS;
+
+	/* PCMCIA Attribute area address */
+	r = platform_get_resource_byname(pdev, IORESOURCE_MEM, "Gayle attribute");
+	sock->phys_attr = r->start;
+
+	/* PCMCIA Memory area address */
+	r = platform_get_resource_byname(pdev, IORESOURCE_MEM, "Gayle memory");
+	sock->phys_mem = r->start;
+
+	/* PCMCIA IO area address */
+	r = platform_get_resource_byname(pdev, IORESOURCE_MEM, "Gayle I/O");
+	sock->phys_io = r->start;
+
+	sock->virt_io = (void *)(ioremap(sock->phys_io, 2*GAYLE_IOSIZE));
+	dev_dbg(&pdev->dev, "%s::%d ioremap: 0x%px\n", __func__, __LINE__,
+		sock->virt_io);
+	sock->virt_io -= GAYLE_IO;
+	dev_dbg(&pdev->dev, "%s::%d ioremap: 0x%px\n", __func__, __LINE__,
+		sock->virt_io);
+	if (!sock->virt_io) {
+		pr_err("pcmcia: cannot remap IO area\n");
+		ret = -ENOMEM;
+		goto out;
+	}
+	sock->socket.io_offset  = (unsigned long)sock->virt_io;
+
+	sock->socket.ops	= &gayle_pcmcia_operations;
+	sock->socket.owner	= THIS_MODULE;
+	sock->socket.pci_irq	= sock->card_irq;
+	sock->socket.features	= SS_CAP_STATIC_MAP | SS_CAP_PCCARD;
+	sock->socket.map_size	= GAYLE_RAMSIZE;
+	sock->socket.dev.parent	= &pdev->dev;
+	sock->socket.resource_ops = &pccard_static_ops;
+
+	platform_set_drvdata(pdev, sock);
+
+	if (request_irq(IRQ_AMIGA_PORTS, gayle_stschg_irq, IRQF_SHARED, "pcmcia_stschg",
+			sock)) {
+		dev_dbg(&pdev->dev, "unable to setup stschg interrupt\n");
+		goto out;
+	}
+
+	if (request_irq(IRQ_AMIGA_EXTER, gayle_ccdet_irq, IRQF_SHARED, "pcmcia_ccdet",
+			sock)) {
+		dev_dbg(&pdev->dev, "unable to setup ccdet interrupt\n");
+		goto out1;
+	}
+
+	dev_dbg(&pdev->dev, "%s::%d\n", __func__, __LINE__);
+	ret = pcmcia_register_socket(&sock->socket);
+	if (ret) {
+		pr_err("pcmcia failed to register\n");
+		goto out2;
+	}
+
+	gayle.inten |= GAYLE_IRQ_CCDET | GAYLE_IRQ_SC | GAYLE_IRQ_DA |
+		GAYLE_IRQ_WR | GAYLE_IRQ_IRQ;
+	pr_err("%s inten: 0x%x\n", __func__, gayle.inten);
+
+	pr_info("Gayle pcmcia @ io/attr/mem %09x %09x %09x  card irqs @ %d\n",
+		sock->phys_io, sock->phys_attr, sock->phys_mem,
+		sock->card_irq);
+
+	return 0;
+out2:
+	free_irq(IRQ_AMIGA_EXTER, &pdev->dev);
+out1:
+	free_irq(IRQ_AMIGA_PORTS, &pdev->dev);
+out:
+	iounmap(sock->virt_io);
+	kfree(sock);
+	return ret;
+}
+
+static int gayle_pcmcia_exit(struct platform_device *pdev) {
+	dev_dbg(&pdev->dev, "%s::%d\n", __func__, __LINE__);
+
+	pcmcia_disable_irq();
+	pcmcia_disable_ccdet_irq();
+	free_irq(IRQ_AMIGA_PORTS, sock);
+	free_irq(IRQ_AMIGA_EXTER, sock);
+	iounmap(sock->virt_io);
+	pcmcia_reset();
+	pcmcia_unregister_socket(&sock->socket);
+	kfree(sock);
+
+	return 0;
+}
+
+static struct platform_driver gayle_pcmcia_driver = {
+	.driver = {
+		.name	= "amiga-gayle-pcmcia",
+	},
+	.probe		= gayle_pcmcia_init,
+	.remove		= gayle_pcmcia_exit,
+};
+
+module_platform_driver(gayle_pcmcia_driver);
+
+MODULE_AUTHOR("Paolo Pisati");
+MODULE_DESCRIPTION("Commodore's Gayle PC Card controller driver");
+MODULE_LICENSE("GPL v2");
-- 
2.34.1





[Index of Archives]     [Video for Linux]     [Yosemite News]     [Linux S/390]     [Linux Kernel]     [Linux SCSI]

  Powered by Linux