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