[PATCH 1/1] pcmcia: socket driver for Commodore's Gayle

[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          |  59 ++++++
 arch/m68k/amiga/platform.c        |  29 +++
 arch/m68k/include/asm/amipcmcia.h |  20 ++
 arch/m68k/include/asm/io_mm.h     |  20 +-
 drivers/pcmcia/Kconfig            |   9 +
 drivers/pcmcia/Makefile           |   1 +
 drivers/pcmcia/gayle.c            | 306 ++++++++++++++++++++++++++++++
 7 files changed, 440 insertions(+), 4 deletions(-)
 create mode 100644 drivers/pcmcia/gayle.c

diff --git a/arch/m68k/amiga/pcmcia.c b/arch/m68k/amiga/pcmcia.c
index 63cce6b590df..21dcd2719319 100644
--- a/arch/m68k/amiga/pcmcia.c
+++ b/arch/m68k/amiga/pcmcia.c
@@ -13,20 +13,37 @@
 */
 
 #include <linux/types.h>
+#include <linux/delay.h>
 #include <linux/jiffies.h>
 #include <linux/timer.h>
 #include <linux/module.h>
 
+#include <pcmcia/ss.h>
 #include <asm/amigayle.h>
 #include <asm/amipcmcia.h>
 
 /* gayle config byte for program voltage and access speed */
 static unsigned char cfg_byte = GAYLE_CFG_0V|GAYLE_CFG_150NS;
 
+/* PCMCIA I/O maps */
+static struct pccard_io_map gayle_io_maps[MAX_IO_WIN];
+
+/*
+ * according to NetBSD (commit e26e7a8a2278 and 100db321d09e in [1])
+ * and depending on Gayle's revision, there are two methods for a PCMCIA
+ * soft-reset
+ *
+ * 1: https://github.com/NetBSD/src.git
+ */
 void pcmcia_reset(void)
 {
 	unsigned long reset_start_time = jiffies;
 
+	gayle.intreq = 0xff;
+	udelay(10);
+	gayle.intreq = 0xfc;
+	udelay(20);
+
 	gayle_reset = 0x00;
 	while (time_before(jiffies, reset_start_time + 1*HZ/100));
 	READ_ONCE(gayle_reset);
@@ -65,6 +82,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;
@@ -119,3 +141,40 @@ void pcmcia_write_disable(void)
 }
 EXPORT_SYMBOL(pcmcia_write_disable);
 
+void gayle_set_io_win(int win, unsigned char flags, unsigned int start, unsigned int stop)
+{
+	struct pccard_io_map *map = &gayle_io_maps[win];
+
+	map->flags = flags;
+	map->start = start;
+	map->stop = stop;
+}
+
+unsigned long gayle_get_byte_base(unsigned long port)
+{
+	struct pccard_io_map *map;
+	int i;
+
+	/* Simple case first */
+	if (!(port & 1)) {
+		return (GAYLE_IO + port);
+	}
+
+	for (i = 0, map = gayle_io_maps; i < MAX_IO_WIN; i++, map++) {
+		if ((map->flags & MAP_ACTIVE) &&
+		    (port >= map->start) && (port <= map->stop)) {
+			if (map->flags & MAP_16BIT) {
+				return (GAYLE_IO + port);
+			} else {
+				/* Assume MAP_AUTOSZ works this way */
+				return (GAYLE_IO + port + ((port & 1) * GAYLE_ODD));
+			}
+		}
+	}
+
+	/* Sometimes an I/O access is done after a card has been removed. */
+	return (GAYLE_IO + port);
+}
+
+EXPORT_SYMBOL(gayle_set_io_win);
+EXPORT_SYMBOL(gayle_get_byte_base);
diff --git a/arch/m68k/amiga/platform.c b/arch/m68k/amiga/platform.c
index d34029d7b058..aff2dcfbcc98 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+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..7df336934fb2 100644
--- a/arch/m68k/include/asm/amipcmcia.h
+++ b/arch/m68k/include/asm/amipcmcia.h
@@ -19,10 +19,14 @@
 
 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);
 void pcmcia_write_disable(void);
+void gayle_set_io_win(int win, unsigned char flags, unsigned int start, unsigned
+		      int stop);
+unsigned long gayle_get_byte_base(unsigned long port);
 
 static inline u_char pcmcia_read_status(void)
 {
@@ -39,6 +43,12 @@ static inline void pcmcia_ack_int(u_char intreq)
 	gayle.intreq = 0xf8;
 }
 
+static inline void pcmcia_ack_ccdet(void)
+{
+	/* reset GAYLE_IRQ_CCDET */
+	gayle.intreq = 0xbb;
+}
+
 static inline void pcmcia_enable_irq(void)
 {
 	gayle.inten |= GAYLE_IRQ_IRQ;
@@ -51,6 +61,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/arch/m68k/include/asm/io_mm.h b/arch/m68k/include/asm/io_mm.h
index 090aec54b8fa..567048692999 100644
--- a/arch/m68k/include/asm/io_mm.h
+++ b/arch/m68k/include/asm/io_mm.h
@@ -56,7 +56,9 @@
 #ifdef CONFIG_AMIGA_PCMCIA
 #include <asm/amigayle.h>
 
-#define AG_ISA_IO_B(ioaddr) ( GAYLE_IO+(ioaddr)+(((ioaddr)&1)*GAYLE_ODD) )
+extern unsigned long gayle_get_byte_base(unsigned long);
+
+#define AG_ISA_IO_B(ioaddr) ( gayle_get_byte_base(ioaddr) )
 #define AG_ISA_IO_W(ioaddr) ( GAYLE_IO+(ioaddr) )
 
 #ifndef MULTI_ISA
@@ -199,20 +201,30 @@ static inline u16 __iomem *isa_mtw(unsigned long addr)
 
 
 #define isa_inb(port)      in_8(isa_itb(port))
+#define isa_outb(val,port) out_8(isa_itb(port),(val))
+#define isa_readb(p)       in_8(isa_mtb((unsigned long)(p)))
+#define isa_writeb(val,p)  out_8(isa_mtb((unsigned long)(p)),(val))
+
+#ifdef CONFIG_PCMCIA_GAYLE
+#define isa_inw(port)	   in_le16(isa_itw(port))
+#define isa_inl(port)	   in_le32(isa_itl(port))
+#define isa_outw(val,port) out_le16(isa_itw(port),(val))
+#define isa_outl(val,port) out_le32(isa_itl(port),(val))
+#define isa_readw(p)	   in_le16(isa_mtw((unsigned long)(p)))
+#define isa_writew(val,p)  out_le16(isa_mtw((unsigned long)(p)),(val))
+#else
 #define isa_inw(port)      (ISA_SEX ? in_be16(isa_itw(port)) : in_le16(isa_itw(port)))
 #define isa_inl(port)      (ISA_SEX ? in_be32(isa_itl(port)) : in_le32(isa_itl(port)))
-#define isa_outb(val,port) out_8(isa_itb(port),(val))
 #define isa_outw(val,port) (ISA_SEX ? out_be16(isa_itw(port),(val)) : out_le16(isa_itw(port),(val)))
 #define isa_outl(val,port) (ISA_SEX ? out_be32(isa_itl(port),(val)) : out_le32(isa_itl(port),(val)))
 
-#define isa_readb(p)       in_8(isa_mtb((unsigned long)(p)))
 #define isa_readw(p)       \
 	(ISA_SEX ? in_be16(isa_mtw((unsigned long)(p)))	\
 		 : in_le16(isa_mtw((unsigned long)(p))))
-#define isa_writeb(val,p)  out_8(isa_mtb((unsigned long)(p)),(val))
 #define isa_writew(val,p)  \
 	(ISA_SEX ? out_be16(isa_mtw((unsigned long)(p)),(val))	\
 		 : out_le16(isa_mtw((unsigned long)(p)),(val)))
+#endif
 
 #ifdef CONFIG_ATARI_ROM_ISA
 #define isa_rom_inb(port)      rom_in_8(isa_itb(port))
diff --git a/drivers/pcmcia/Kconfig b/drivers/pcmcia/Kconfig
index dddb235dd020..62da9717d78e 100644
--- a/drivers/pcmcia/Kconfig
+++ b/drivers/pcmcia/Kconfig
@@ -253,4 +253,13 @@ config PCCARD_NONSTATIC
 config PCCARD_IODYN
 	bool
 
+config PCMCIA_GAYLE
+	tristate "Amiga Gayle bridge support"
+	depends on PCMCIA && AMIGA && AMIGA_PCMCIA && !APNE
+	select PCCARD_IODYN
+	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 c9d51b150682..1ebbf6c776bb 100644
--- a/drivers/pcmcia/Makefile
+++ b/drivers/pcmcia/Makefile
@@ -33,6 +33,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..1180da95f84a
--- /dev/null
+++ b/drivers/pcmcia/gayle.c
@@ -0,0 +1,306 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Commodore's Gayle PC Card controller driver
+ *
+ * Partially derived from Kars de Jong <jongk@xxxxxxxxxxxxxx>, v2.6.10 era
+ * previous attempt
+ *
+ * Copyright (c) 2025 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>
+
+struct gayle_pcmcia_sock {
+	struct pcmcia_socket	socket;
+
+	phys_addr_t	attr;
+	phys_addr_t	io;
+	phys_addr_t	mem;
+};
+
+#define to_gayle_socket(x) container_of(x, struct gayle_pcmcia_sock, socket)
+
+/* apply socket configuration flags */
+static int gayle_sock_set_socket(struct pcmcia_socket *skt,
+				 struct socket_state_t *state)
+{
+	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();
+		pcmcia_write_enable();
+	}
+
+	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;
+}
+
+/* get card status GetStatus flags */
+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:
+		default:
+			break;
+	}
+
+	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)
+{
+	dev_dbg(&skt->dev, "%s::%d map: 0x%x flags: 0x%x speed: 0x%x "
+		"start: 0x%x stop: 0x%x\n", __func__, __LINE__, map->map,
+		map->flags, map->speed, map->start, map->stop);
+
+	if (map->map >= MAX_IO_WIN) {
+		pr_err("%s(): map (%d) out of range\n", __func__,
+		       map->map);
+		return -1;
+	}
+
+	if (map->stop == 1)
+		map->stop = PAGE_SIZE-1;
+
+	gayle_set_io_win(map->map, map->flags, map->start, map->stop);
+
+	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);
+	unsigned long start;
+
+	dev_dbg(&skt->dev, "%s::%d map: 0x%x flags: 0x%x speed: 0x%x\n",
+		 __func__, __LINE__, map->map, map->flags, map->speed);
+	dev_dbg(&skt->dev, "static_start: 0x%x card_start: 0x%x\n",
+		map->static_start, map->card_start);
+	dev_dbg(&skt->dev, "resource: %pr\n", map->res);
+
+	if (map->map >= MAX_WIN) {
+		pr_err("%s(): map (%d) out of range\n", __func__,
+		       map->map);
+		return -EINVAL;
+	}
+
+	pcmcia_access_speed(map->speed);
+
+	if (map->flags & MAP_ATTRIB) {
+		start = sock->attr;
+		if (map->flags & MAP_ACTIVE)
+			pcmcia_access_speed(720);
+	} else
+		start = sock->mem;
+
+	map->static_start = start + 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;
+
+/*
+ * The sole purpose of this handler is to ack the incoming PCMCIA card's
+ * interrupt at gayle's level to avoid an interrupt storm
+ */
+/* on irq 2 / IRQ_AMIGA_PORTS, serves GAYLE_IRQ_IRQ */
+static irqreturn_t gayle_irq_dummy(int irq, void *data)
+{
+	unsigned char pcmcia_intreq;
+	struct gayle_pcmcia_sock *sock = data;
+
+	pcmcia_intreq = pcmcia_get_intreq();
+	if (!(pcmcia_intreq & GAYLE_IRQ_IRQ))
+		return IRQ_NONE;
+
+	dev_dbg(&sock->socket.dev,"%s::%d intreq: 0x%x\n", __func__, __LINE__,
+		pcmcia_intreq);
+
+	pcmcia_ack_int(pcmcia_get_intreq());
+
+	return IRQ_NONE;
+}
+
+/* on irq 6 / IRQ_AMIGA_EXTER, serves GAYLE_IRQ_CCDET */
+static irqreturn_t gayle_irq_ccdet(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;
+
+	dev_dbg(&sock->socket.dev,"%s::%d intreq: 0x%x\n", __func__, __LINE__,
+		pcmcia_intreq);
+
+	pcmcia_ack_ccdet();
+	pcmcia_parse_events(&sock->socket, SS_DETECT);
+
+	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;
+
+	r = platform_get_resource_byname(pdev, IORESOURCE_MEM, "Gayle attribute");
+	sock->attr = r->start;
+
+	r = platform_get_resource_byname(pdev, IORESOURCE_MEM, "Gayle I/O");
+	sock->io = r->start;
+
+	r = platform_get_resource_byname(pdev, IORESOURCE_MEM, "Gayle memory");
+	sock->mem = r->start;
+
+	sock->socket.owner	= THIS_MODULE;
+	sock->socket.dev.parent	= &pdev->dev;
+	sock->socket.ops	= &gayle_pcmcia_operations;
+	sock->socket.resource_ops = &pccard_iodyn_ops;
+	sock->socket.features	= SS_CAP_STATIC_MAP | SS_CAP_PCCARD;
+	sock->socket.pci_irq	= IRQ_AMIGA_PORTS;
+	sock->socket.irq_mask	= 0;
+	sock->socket.map_size	= PAGE_SIZE;
+	sock->socket.io_offset	= 0;
+
+	platform_set_drvdata(pdev, sock);
+
+	if (request_irq(IRQ_AMIGA_PORTS, gayle_irq_dummy, IRQF_SHARED,
+			"Gayle PCMCIA dummy", sock)) {
+		dev_dbg(&pdev->dev, "unable to setup dummy irq handler\n");
+		goto out;
+	}
+
+	if (request_irq(IRQ_AMIGA_EXTER, gayle_irq_ccdet, IRQF_SHARED,
+			"Gayle PCMCIA socket", sock)) {
+		dev_dbg(&pdev->dev, "unable to setup ccdet irq handler\n");
+		goto out1;
+	}
+
+	ret = pcmcia_register_socket(&sock->socket);
+	if (ret) {
+		pr_err("pcmcia failed to register\n");
+		goto out2;
+	}
+
+	/* put Gayle in a known state */
+	gayle.cardstatus = 0x0;
+	gayle.config = 0x0;
+	gayle.intreq = 0x0;
+	gayle.inten |= GAYLE_IRQ_CCDET | GAYLE_IRQ_IRQ;
+
+	pr_info("Gayle pcmcia @ io/attr/mem %09x %09x %09x\n",
+		sock->io, sock->attr, sock->mem);
+
+	return 0;
+out2:
+	free_irq(IRQ_AMIGA_EXTER, &pdev->dev);
+out1:
+	free_irq(IRQ_AMIGA_PORTS, &pdev->dev);
+out:
+	kfree(sock);
+
+	return ret;
+}
+
+static void 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);
+	pcmcia_unregister_socket(&sock->socket);
+	kfree(sock);
+}
+
+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