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