From: Olof Johansson <hidden> Date: 2007-06-25 00:54:14
Driver for the CompactFlash slot on the PA Semi Electra eval board. It's
a simple device sitting on localbus, with interrupts and detect/voltage
control over GPIO.
The driver is implemented as an of_platform driver, and adds localbus
as a bus being probed by the of_platform framework.
Signed-off-by: Olof Johansson <redacted>
Acked-by: Paul Mackerras <redacted>
---
Fixed the comments from Russell on last post, this should be good to
merge now.
Index: powerpc/drivers/pcmcia/Kconfig
===================================================================
@@ -0,0 +1,382 @@+/*+*Copyright(C)2007PASemi,Inc+*+*Maintainedby:OlofJohansson<olof@lixom.net>+*+*Basedondrivers/pcmcia/omap_cf.c+*+*Thisprogramisfreesoftware;youcanredistributeitand/ormodify+*itunderthetermsoftheGNUGeneralPublicLicenseaspublishedby+*theFreeSoftwareFoundation;eitherversion2oftheLicense,or+*(atyouroption)anylaterversion.+*+*Thisprogramisdistributedinthehopethatitwillbeuseful,+*butWITHOUTANYWARRANTY;withouteventheimpliedwarrantyof+*MERCHANTABILITYorFITNESSFORAPARTICULARPURPOSE.Seethe+*GNUGeneralPublicLicenseformoredetails.+*+*YoushouldhavereceivedacopyoftheGNUGeneralPublicLicense+*alongwiththisprogram;ifnot,writetotheFreeSoftware+*Foundation,Inc.,59TemplePlace,Suite330,Boston,MA02111-1307USA+*/++#include<linux/module.h>+#include<linux/kernel.h>+#include<linux/sched.h>+#include<linux/platform_device.h>+#include<linux/errno.h>+#include<linux/init.h>+#include<linux/delay.h>+#include<linux/interrupt.h>++#include<pcmcia/ss.h>+#include<asm/of_platform.h>++staticconstchardriver_name[]="electra-cf";++structelectra_cf_socket{+structpcmcia_socketsocket;++structtimer_listtimer;+unsignedpresent:1;+unsignedactive:1;++structof_device*ofdev;+unsignedlongmem_phys;+void__iomem*mem_base;+unsignedlongmem_size;+void__iomem*io_virt;+unsignedintio_base;+unsignedintio_size;+u_intirq;+structresourceiomem;+void__iomem*gpio_base;+intgpio_detect;+intgpio_vsense;+intgpio_3v;+intgpio_5v;+};++#define POLL_INTERVAL (2 * HZ)+++staticintelectra_cf_present(structelectra_cf_socket*cf)+{+unsignedintgpio;++gpio=in_le32(cf->gpio_base+0x40);+return!(gpio&(1<<cf->gpio_detect));+}++staticintelectra_cf_ss_init(structpcmcia_socket*s)+{+return0;+}++/* the timer is primarily to kick this socket's pccardd */+staticvoidelectra_cf_timer(unsignedlong_cf)+{+structelectra_cf_socket*cf=(void*)_cf;+intpresent=electra_cf_present(cf);++if(present!=cf->present){+cf->present=present;+pcmcia_parse_events(&cf->socket,SS_DETECT);+}++if(cf->active)+mod_timer(&cf->timer,jiffies+POLL_INTERVAL);+}++staticirqreturn_telectra_cf_irq(intirq,void*_cf)+{+electra_cf_timer((unsignedlong)_cf);+returnIRQ_HANDLED;+}++staticintelectra_cf_get_status(structpcmcia_socket*s,u_int*sp)+{+structelectra_cf_socket*cf;++if(!sp)+return-EINVAL;++cf=container_of(s,structelectra_cf_socket,socket);++/* NOTE CF is always 3VCARD */+if(electra_cf_present(cf)){+structelectra_cf_socket*cf;++*sp=SS_READY|SS_DETECT|SS_POWERON|SS_3VCARD;+cf=container_of(s,structelectra_cf_socket,socket);+s->pci_irq=cf->irq;+}else+*sp=0;+return0;+}++staticintelectra_cf_set_socket(structpcmcia_socket*sock,+structsocket_state_t*s)+{+unsignedintgpio;+unsignedintvcc;+structelectra_cf_socket*cf;++cf=container_of(sock,structelectra_cf_socket,socket);++/* "reset" means no power in our case */+vcc=(s->flags&SS_RESET)?0:s->Vcc;++switch(vcc){+case0:+gpio=0;+break;+case33:+gpio=(1<<cf->gpio_3v);+break;+default:+/* CF is 3.3V only */+return-EINVAL;+}++gpio|=1<<(cf->gpio_3v+16);/* enwr */+gpio|=1<<(cf->gpio_5v+16);/* enwr */+out_le32(cf->gpio_base+0x90,gpio);++pr_debug("%s: Vcc %d, io_irq %d, flags %04x csc %04x\n",+driver_name,s->Vcc,s->io_irq,s->flags,s->csc_mask);++return0;+}++staticintelectra_cf_set_io_map(structpcmcia_socket*s,+structpccard_io_map*io)+{+return0;+}++staticintelectra_cf_set_mem_map(structpcmcia_socket*s,+structpccard_mem_map*map)+{+structelectra_cf_socket*cf;++if(map->card_start)+return-EINVAL;+cf=container_of(s,structelectra_cf_socket,socket);+map->static_start=cf->mem_phys;+map->flags&=MAP_ACTIVE|MAP_ATTRIB;+if(!(map->flags&MAP_ATTRIB))+map->static_start+=0x800;+return0;+}++staticstructpccard_operationselectra_cf_ops={+.init=electra_cf_ss_init,+.get_status=electra_cf_get_status,+.set_socket=electra_cf_set_socket,+.set_io_map=electra_cf_set_io_map,+.set_mem_map=electra_cf_set_mem_map,+};++staticint__devinitelectra_cf_probe(structof_device*ofdev,+conststructof_device_id*match)+{+structdevice*device=&ofdev->dev;+structdevice_node*np=ofdev->node;+structelectra_cf_socket*cf;+structresourcemem,io;+intstatus;+constunsignedint*prop;+interr;++err=of_address_to_resource(np,0,&mem);+if(err)+return-EINVAL;++err=of_address_to_resource(np,1,&io);+if(err)+return-EINVAL;++cf=kzalloc(sizeof*cf,GFP_KERNEL);+if(!cf)+return-ENOMEM;++init_timer(&cf->timer);+cf->timer.function=electra_cf_timer;+cf->timer.data=(unsignedlong)cf;++cf->ofdev=ofdev;+cf->mem_phys=mem.start;+cf->mem_base=ioremap(mem.start,mem.end-mem.start);+cf->io_size=PAGE_ALIGN(io.end-io.start);++cf->io_virt=reserve_phb_iospace(cf->io_size);++cf->gpio_base=ioremap(0xfc103000,0x1000);+dev_set_drvdata(device,cf);++if(!cf->mem_base||!cf->io_virt||!cf->gpio_base){+dev_err(device,"can't ioremap ranges\n");+status=-ENOMEM;+gotofail1;+}++__ioremap_explicit(io.start,(unsignedlong)cf->io_virt,cf->io_size,+_PAGE_NO_CACHE|_PAGE_GUARDED);++cf->io_base=(unsignedlong)cf->io_virt-VMALLOC_END;++cf->iomem.start=(unsignedlong)cf->mem_base;+cf->iomem.end=(unsignedlong)cf->mem_base+(mem.end-mem.start);+cf->iomem.flags=IORESOURCE_MEM;++cf->irq=irq_of_parse_and_map(np,0);++status=request_irq(cf->irq,electra_cf_irq,IRQF_SHARED,+driver_name,cf);+if(status<0){+dev_err(device,"request_irq failed\n");+gotofail1;+}++cf->socket.pci_irq=cf->irq;++prop=get_property(np,"card-detect-gpio",NULL);+cf->gpio_detect=*prop;+prop=get_property(np,"card-vsense-gpio",NULL);+cf->gpio_vsense=*prop;+prop=get_property(np,"card-3v-gpio",NULL);+cf->gpio_3v=*prop;+prop=get_property(np,"card-5v-gpio",NULL);+cf->gpio_5v=*prop;++cf->socket.io_offset=cf->io_base;++/* reserve chip-select regions */+if(!request_mem_region(mem.start,mem.end+1-mem.start,+driver_name)){+status=-ENXIO;+dev_err(device,"Can't claim memory region\n");+gotofail1;+}++if(!request_region(cf->io_base,cf->io_size,driver_name)){+status=-ENXIO;+dev_err(device,"Can't claim I/O region\n");+gotofail2;+}++cf->socket.owner=THIS_MODULE;+cf->socket.dev.parent=&ofdev->dev;+cf->socket.ops=&electra_cf_ops;+cf->socket.resource_ops=&pccard_static_ops;+cf->socket.features=SS_CAP_PCCARD|SS_CAP_STATIC_MAP|+SS_CAP_MEM_ALIGN;+cf->socket.map_size=0x800;++status=pcmcia_register_socket(&cf->socket);+if(status<0){+dev_err(device,"pcmcia_register_socket failed\n");+gotofail3;+}++dev_info(device,"at mem 0x%lx io 0x%lx irq %d\n",+mem.start,io.start,cf->irq);++cf->active=1;+electra_cf_timer((unsignedlong)cf);+return0;++fail3:+release_mem_region(io.start,io.end+1-io.start);+fail2:+release_mem_region(mem.start,mem.end+1-mem.start);+fail1:+/* XXX No way to undo the io reservation at this time */+if(cf->mem_base)+iounmap(cf->mem_base);+if(cf->gpio_base)+iounmap(cf->gpio_base);+device_init_wakeup(&ofdev->dev,0);+kfree(cf);+returnstatus;++}++staticint__devexitelectra_cf_remove(structof_device*ofdev)+{+structdevice*device=&ofdev->dev;+structelectra_cf_socket*cf;++cf=dev_get_drvdata(device);++cf->active=0;+pcmcia_unregister_socket(&cf->socket);+free_irq(cf->irq,cf);+del_timer_sync(&cf->timer);++iounmap(cf->mem_base);+iounmap(cf->gpio_base);+release_mem_region(cf->mem_phys,cf->mem_size);+release_region(cf->io_base,cf->io_size);++kfree(cf);++return0;+}++staticintbus_notify(structnotifier_block*nb,unsignedlongaction,+void*data)+{+structdevice*dev=data;++printk("bus notify called\n");++/* We are only intereted in device addition */+if(action!=BUS_NOTIFY_ADD_DEVICE)+return0;++/* We use the direct ops for localbus */+dev->archdata.dma_ops=&dma_direct_ops;++return0;+}++staticstructnotifier_blockbus_notifier={+.notifier_call=bus_notify,+};++staticstructof_device_idelectra_cf_match[]=+{+{+.compatible="electra-cf",+},+{},+};++staticstructof_platform_driverelectra_cf_driver=+{+.name=(char*)driver_name,+.match_table=electra_cf_match,+.probe=electra_cf_probe,+.remove=electra_cf_remove,+};++staticint__initelectra_cf_init(void)+{+bus_register_notifier(&pcmcia_bus_type,&bus_notifier);+returnof_register_platform_driver(&electra_cf_driver);+}+module_init(electra_cf_init);++staticvoid__exitelectra_cf_exit(void)+{+bus_unregister_notifier(&pcmcia_bus_type,&bus_notifier);+of_unregister_platform_driver(&electra_cf_driver);+}+module_exit(electra_cf_exit);++MODULE_LICENSE("GPL");+MODULE_AUTHOR("Olof Johansson <olof@lixom.net>");+MODULE_DESCRIPTION("PA Semi Electra CF driver");+
From: Christoph Hellwig <hch@infradead.org> Date: 2007-06-25 05:56:46
+static int bus_notify(struct notifier_block *nb, unsigned long action,
+ void *data)
+{
+ struct device *dev = data;
+
+ printk("bus notify called\n");
+
+ /* We are only intereted in device addition */
+ if (action != BUS_NOTIFY_ADD_DEVICE)
+ return 0;
+
+ /* We use the direct ops for localbus */
+ dev->archdata.dma_ops = &dma_direct_ops;
+
+ return 0;
+}
Sorry for not coming back to you after the last posting of the patch,
but I still really this bit of the code. We don't set dma ops from
a driver anywhere else in the tree, so I'd really prefer if you could
handle this in architecture code somewhere. Especially as dma_direct_ops
and the pcmcia_bug_type shouldn't really be something exported to users.
Also this code is unlogic if not buggy. Just because you have one
electra bridge it doesn't mean all pcmcia is driven by it. In fact I'm
pretty sure there are cardbus/pcmcia bridges that can be plugged into
plain pci slots.
From: Olof Johansson <hidden> Date: 2007-06-25 15:40:59
On Mon, Jun 25, 2007 at 06:56:36AM +0100, Christoph Hellwig wrote:
quoted
+static int bus_notify(struct notifier_block *nb, unsigned long action,
+ void *data)
+{
+ struct device *dev = data;
+
+ printk("bus notify called\n");
+
+ /* We are only intereted in device addition */
+ if (action != BUS_NOTIFY_ADD_DEVICE)
+ return 0;
+
+ /* We use the direct ops for localbus */
+ dev->archdata.dma_ops = &dma_direct_ops;
+
+ return 0;
+}
Sorry for not coming back to you after the last posting of the patch,
but I still really this bit of the code. We don't set dma ops from
a driver anywhere else in the tree, so I'd really prefer if you could
handle this in architecture code somewhere. Especially as dma_direct_ops
and the pcmcia_bug_type shouldn't really be something exported to users.
Also this code is unlogic if not buggy. Just because you have one
electra bridge it doesn't mean all pcmcia is driven by it. In fact I'm
pretty sure there are cardbus/pcmcia bridges that can be plugged into
plain pci slots.
Yeah, all good points. Also, I obviously missed to take out the debug
printk. I'll post an updated patch separately.
Thanks!
-Olof
From: Olof Johansson <hidden> Date: 2007-06-25 17:03:17
Driver for the CompactFlash slot on the PA Semi Electra eval board. It's
a simple device sitting on localbus, with interrupts and detect/voltage
control over GPIO.
The driver is implemented as an of_platform driver, and adds localbus
as a bus being probed by the of_platform framework.
Signed-off-by: Olof Johansson <redacted>
---
Chances since last post:
* Comments from Christoph:
- Moved bus notifier to arch code
- Checking for bus (socket driver) type before changing dma ops
Index: mainline/drivers/pcmcia/Kconfig
===================================================================
@@ -0,0 +1,359 @@+/*+*Copyright(C)2007PASemi,Inc+*+*Maintainedby:OlofJohansson<olof@lixom.net>+*+*Basedondrivers/pcmcia/omap_cf.c+*+*Thisprogramisfreesoftware;youcanredistributeitand/ormodify+*itunderthetermsoftheGNUGeneralPublicLicenseaspublishedby+*theFreeSoftwareFoundation;eitherversion2oftheLicense,or+*(atyouroption)anylaterversion.+*+*Thisprogramisdistributedinthehopethatitwillbeuseful,+*butWITHOUTANYWARRANTY;withouteventheimpliedwarrantyof+*MERCHANTABILITYorFITNESSFORAPARTICULARPURPOSE.Seethe+*GNUGeneralPublicLicenseformoredetails.+*+*YoushouldhavereceivedacopyoftheGNUGeneralPublicLicense+*alongwiththisprogram;ifnot,writetotheFreeSoftware+*Foundation,Inc.,59TemplePlace,Suite330,Boston,MA02111-1307USA+*/++#include<linux/module.h>+#include<linux/kernel.h>+#include<linux/sched.h>+#include<linux/platform_device.h>+#include<linux/errno.h>+#include<linux/init.h>+#include<linux/delay.h>+#include<linux/interrupt.h>++#include<pcmcia/ss.h>+#include<asm/of_platform.h>++staticconstchardriver_name[]="electra-cf";++structelectra_cf_socket{+structpcmcia_socketsocket;++structtimer_listtimer;+unsignedpresent:1;+unsignedactive:1;++structof_device*ofdev;+unsignedlongmem_phys;+void__iomem*mem_base;+unsignedlongmem_size;+void__iomem*io_virt;+unsignedintio_base;+unsignedintio_size;+u_intirq;+structresourceiomem;+void__iomem*gpio_base;+intgpio_detect;+intgpio_vsense;+intgpio_3v;+intgpio_5v;+};++#define POLL_INTERVAL (2 * HZ)+++staticintelectra_cf_present(structelectra_cf_socket*cf)+{+unsignedintgpio;++gpio=in_le32(cf->gpio_base+0x40);+return!(gpio&(1<<cf->gpio_detect));+}++staticintelectra_cf_ss_init(structpcmcia_socket*s)+{+return0;+}++/* the timer is primarily to kick this socket's pccardd */+staticvoidelectra_cf_timer(unsignedlong_cf)+{+structelectra_cf_socket*cf=(void*)_cf;+intpresent=electra_cf_present(cf);++if(present!=cf->present){+cf->present=present;+pcmcia_parse_events(&cf->socket,SS_DETECT);+}++if(cf->active)+mod_timer(&cf->timer,jiffies+POLL_INTERVAL);+}++staticirqreturn_telectra_cf_irq(intirq,void*_cf)+{+electra_cf_timer((unsignedlong)_cf);+returnIRQ_HANDLED;+}++staticintelectra_cf_get_status(structpcmcia_socket*s,u_int*sp)+{+structelectra_cf_socket*cf;++if(!sp)+return-EINVAL;++cf=container_of(s,structelectra_cf_socket,socket);++/* NOTE CF is always 3VCARD */+if(electra_cf_present(cf)){+structelectra_cf_socket*cf;++*sp=SS_READY|SS_DETECT|SS_POWERON|SS_3VCARD;+cf=container_of(s,structelectra_cf_socket,socket);+s->pci_irq=cf->irq;+}else+*sp=0;+return0;+}++staticintelectra_cf_set_socket(structpcmcia_socket*sock,+structsocket_state_t*s)+{+unsignedintgpio;+unsignedintvcc;+structelectra_cf_socket*cf;++cf=container_of(sock,structelectra_cf_socket,socket);++/* "reset" means no power in our case */+vcc=(s->flags&SS_RESET)?0:s->Vcc;++switch(vcc){+case0:+gpio=0;+break;+case33:+gpio=(1<<cf->gpio_3v);+break;+default:+/* CF is 3.3V only */+return-EINVAL;+}++gpio|=1<<(cf->gpio_3v+16);/* enwr */+gpio|=1<<(cf->gpio_5v+16);/* enwr */+out_le32(cf->gpio_base+0x90,gpio);++pr_debug("%s: Vcc %d, io_irq %d, flags %04x csc %04x\n",+driver_name,s->Vcc,s->io_irq,s->flags,s->csc_mask);++return0;+}++staticintelectra_cf_set_io_map(structpcmcia_socket*s,+structpccard_io_map*io)+{+return0;+}++staticintelectra_cf_set_mem_map(structpcmcia_socket*s,+structpccard_mem_map*map)+{+structelectra_cf_socket*cf;++if(map->card_start)+return-EINVAL;+cf=container_of(s,structelectra_cf_socket,socket);+map->static_start=cf->mem_phys;+map->flags&=MAP_ACTIVE|MAP_ATTRIB;+if(!(map->flags&MAP_ATTRIB))+map->static_start+=0x800;+return0;+}++staticstructpccard_operationselectra_cf_ops={+.init=electra_cf_ss_init,+.get_status=electra_cf_get_status,+.set_socket=electra_cf_set_socket,+.set_io_map=electra_cf_set_io_map,+.set_mem_map=electra_cf_set_mem_map,+};++staticint__devinitelectra_cf_probe(structof_device*ofdev,+conststructof_device_id*match)+{+structdevice*device=&ofdev->dev;+structdevice_node*np=ofdev->node;+structelectra_cf_socket*cf;+structresourcemem,io;+intstatus;+constunsignedint*prop;+interr;++err=of_address_to_resource(np,0,&mem);+if(err)+return-EINVAL;++err=of_address_to_resource(np,1,&io);+if(err)+return-EINVAL;++cf=kzalloc(sizeof*cf,GFP_KERNEL);+if(!cf)+return-ENOMEM;++init_timer(&cf->timer);+cf->timer.function=electra_cf_timer;+cf->timer.data=(unsignedlong)cf;++cf->ofdev=ofdev;+cf->mem_phys=mem.start;+cf->mem_base=ioremap(mem.start,mem.end-mem.start);+cf->io_size=PAGE_ALIGN(io.end-io.start);++cf->io_virt=reserve_phb_iospace(cf->io_size);++cf->gpio_base=ioremap(0xfc103000,0x1000);+dev_set_drvdata(device,cf);++if(!cf->mem_base||!cf->io_virt||!cf->gpio_base){+dev_err(device,"can't ioremap ranges\n");+status=-ENOMEM;+gotofail1;+}++__ioremap_explicit(io.start,(unsignedlong)cf->io_virt,cf->io_size,+_PAGE_NO_CACHE|_PAGE_GUARDED);++cf->io_base=(unsignedlong)cf->io_virt-VMALLOC_END;++cf->iomem.start=(unsignedlong)cf->mem_base;+cf->iomem.end=(unsignedlong)cf->mem_base+(mem.end-mem.start);+cf->iomem.flags=IORESOURCE_MEM;++cf->irq=irq_of_parse_and_map(np,0);++status=request_irq(cf->irq,electra_cf_irq,IRQF_SHARED,+driver_name,cf);+if(status<0){+dev_err(device,"request_irq failed\n");+gotofail1;+}++cf->socket.pci_irq=cf->irq;++prop=get_property(np,"card-detect-gpio",NULL);+cf->gpio_detect=*prop;+prop=get_property(np,"card-vsense-gpio",NULL);+cf->gpio_vsense=*prop;+prop=get_property(np,"card-3v-gpio",NULL);+cf->gpio_3v=*prop;+prop=get_property(np,"card-5v-gpio",NULL);+cf->gpio_5v=*prop;++cf->socket.io_offset=cf->io_base;++/* reserve chip-select regions */+if(!request_mem_region(mem.start,mem.end+1-mem.start,+driver_name)){+status=-ENXIO;+dev_err(device,"Can't claim memory region\n");+gotofail1;+}++if(!request_region(cf->io_base,cf->io_size,driver_name)){+status=-ENXIO;+dev_err(device,"Can't claim I/O region\n");+gotofail2;+}++cf->socket.owner=THIS_MODULE;+cf->socket.dev.parent=&ofdev->dev;+cf->socket.ops=&electra_cf_ops;+cf->socket.resource_ops=&pccard_static_ops;+cf->socket.features=SS_CAP_PCCARD|SS_CAP_STATIC_MAP|+SS_CAP_MEM_ALIGN;+cf->socket.map_size=0x800;++status=pcmcia_register_socket(&cf->socket);+if(status<0){+dev_err(device,"pcmcia_register_socket failed\n");+gotofail3;+}++dev_info(device,"at mem 0x%lx io 0x%lx irq %d\n",+mem.start,io.start,cf->irq);++cf->active=1;+electra_cf_timer((unsignedlong)cf);+return0;++fail3:+release_mem_region(io.start,io.end+1-io.start);+fail2:+release_mem_region(mem.start,mem.end+1-mem.start);+fail1:+/* XXX No way to undo the io reservation at this time */+if(cf->mem_base)+iounmap(cf->mem_base);+if(cf->gpio_base)+iounmap(cf->gpio_base);+device_init_wakeup(&ofdev->dev,0);+kfree(cf);+returnstatus;++}++staticint__devexitelectra_cf_remove(structof_device*ofdev)+{+structdevice*device=&ofdev->dev;+structelectra_cf_socket*cf;++cf=dev_get_drvdata(device);++cf->active=0;+pcmcia_unregister_socket(&cf->socket);+free_irq(cf->irq,cf);+del_timer_sync(&cf->timer);++iounmap(cf->mem_base);+iounmap(cf->gpio_base);+release_mem_region(cf->mem_phys,cf->mem_size);+release_region(cf->io_base,cf->io_size);++kfree(cf);++return0;+}++staticstructof_device_idelectra_cf_match[]=+{+{+.compatible="electra-cf",+},+{},+};++staticstructof_platform_driverelectra_cf_driver=+{+.name=(char*)driver_name,+.match_table=electra_cf_match,+.probe=electra_cf_probe,+.remove=electra_cf_remove,+};++staticint__initelectra_cf_init(void)+{+returnof_register_platform_driver(&electra_cf_driver);+}+module_init(electra_cf_init);++staticvoid__exitelectra_cf_exit(void)+{+of_unregister_platform_driver(&electra_cf_driver);+}+module_exit(electra_cf_exit);++MODULE_LICENSE("GPL");+MODULE_AUTHOR("Olof Johansson <olof@lixom.net>");+MODULE_DESCRIPTION("PA Semi Electra CF driver");+
@@ -204,7 +208,41 @@ static void __init pas_init_early(void)iommu_init_early_pasemi();}+staticintpcmcia_notify(structnotifier_block*nb,unsignedlongaction,+void*data)+{+structdevice*dev=data;+structdevice*parent;+structpcmcia_device*pdev=to_pcmcia_dev(dev);++/* We are only intereted in device addition */+if(action!=BUS_NOTIFY_ADD_DEVICE)+return0;++parent=pdev->socket->dev.parent;++/* We know electra_cf devices will always have of_node set, since+*electra_cfisanof_platformdriver.+*/+if(!parent->archdata.of_node)+return0;++if(!of_device_is_compatible(parent->archdata.of_node,"electra-cf"))+return0;++/* We use the direct ops for localbus */+dev->archdata.dma_ops=&dma_direct_ops;++return0;+}++staticstructnotifier_blockpcmcia_notifier={+.notifier_call=pcmcia_notify,+};++staticstructof_device_idpasemi_bus_ids[]={+{.type="localbus",},{.type="sdc",},{},};
@@ -214,6 +252,10 @@ static int __init pasemi_publish_devicesif(!machine_is(pasemi))return0;+#ifdef CONFIG_PCMCIA+bus_register_notifier(&pcmcia_bus_type,&pcmcia_notifier);+#endif+/* Publish OF platform devices for SDC and other non-PCI devices */of_platform_bus_probe(NULL,pasemi_bus_ids,NULL);
From: Christoph Hellwig <hch@infradead.org> Date: 2007-06-25 19:39:22
On Mon, Jun 25, 2007 at 12:12:21PM -0500, Olof Johansson wrote:
Driver for the CompactFlash slot on the PA Semi Electra eval board. It's
a simple device sitting on localbus, with interrupts and detect/voltage
control over GPIO.
The driver is implemented as an of_platform driver, and adds localbus
as a bus being probed by the of_platform framework.
Looks good to me. The only odd thing is the inconsistant use of
#ifdef CONFIG_PCMCIA in setup.c.
From: Olof Johansson <hidden> Date: 2007-06-25 20:34:33
On Mon, Jun 25, 2007 at 08:39:09PM +0100, Christoph Hellwig wrote:
On Mon, Jun 25, 2007 at 12:12:21PM -0500, Olof Johansson wrote:
quoted
Driver for the CompactFlash slot on the PA Semi Electra eval board. It's
a simple device sitting on localbus, with interrupts and detect/voltage
control over GPIO.
The driver is implemented as an of_platform driver, and adds localbus
as a bus being probed by the of_platform framework.
Looks good to me. The only odd thing is the inconsistant use of
#ifdef CONFIG_PCMCIA in setup.c.
Thanks, and thanks for the review.
The ifdef is needed since for CONFIG_PCMCIA=n builds, the bus notifier
isn't available. I wanted to do the bus notifier registration explicitly
before the of_platform bus probe to avoid later surprises due to reordered
initcalls in case it was split up in it's own initcall.
I could add the code under ifdef as well, but it didn't seem too
critical. Once the second major board comes along I'll probably move it
out to a per-board file, there's no real need for it just yet.
-Olof
From: Milton Miller <hidden> Date: 2007-06-27 11:20:22
Hi Olof.
On Mon Jun 25 11:03:11 EST 2007, Olof Johansson wrote:
Driver for the CompactFlash slot on the PA Semi Electra eval board.
It's
a simple device sitting on localbus, with interrupts and detect/voltage
control over GPIO.
The driver is implemented as an of_platform driver, and adds localbus
as a bus being probed by the of_platform framework.
get_property is now a #define for of_get_property. I think the use of
get_property is being deprecated.
If of_get_property fails to find the property, then you will
dereference a NULL pointer in a probe function and will leave the
bus_type locked.
From: Milton Miller <hidden> Date: 2007-06-27 11:20:42
On Mon, Jun 25, 2007 at 08:39:09PM +0100, Christoph Hellwig wrote:
quoted
On Mon, Jun 25, 2007 at 12:12:21PM -0500, Olof Johansson wrote:
quoted
Driver for the CompactFlash slot on the PA Semi Electra eval board.
It's
a simple device sitting on localbus, with interrupts and
detect/voltage
control over GPIO.
The driver is implemented as an of_platform driver, and adds localbus
as a bus being probed by the of_platform framework.
Looks good to me. The only odd thing is the inconsistant use of
#ifdef CONFIG_PCMCIA in setup.c.
The ifdef is needed since for CONFIG_PCMCIA=n builds, the bus notifier
isn't available. I wanted to do the bus notifier registration
explicitly
before the of_platform bus probe to avoid later surprises due to
reordered
initcalls in case it was split up in it's own initcall.
If CONFIG_PCMCIA=m then your notifier is not registered. The modprobe
of your of_driver loads ds.ko, registers the bus, then registers your
driver. When the socket driver tries to dma, the BUG in dma_64 for no
archdata.dma_ops triggers.
It seems like we need
(1) a notifier that a bus is registered, run before allowing any
devices, so that platforms can register bus notifiers by bus name
before the devices and drivers are registered.
(2) a powerpc64 generic pcmcia bus notifier that copys the dma ops from
the parent socket.
(3) something to set the dma_ops to direct_dma_ops on the of device.
If we don't want (3) to be in the driver (as Christoph previosly
mentioned), then it needs to be a seperate bus that reuses the of
matching. This would be similar to how ibmebus is setup. If I
remember the discussion, ibmebus is to provide the alternate dma ops
and steals match etc code from the of_platform bus type.
Oh, is this why you have depends on PCMCIA=y ?
I could add the code under ifdef as well, but it didn't seem too
critical. Once the second major board comes along I'll probably move it
out to a per-board file, there's no real need for it just yet.
From: Olof Johansson <hidden> Date: 2007-07-05 14:26:57
On Wed, Jun 27, 2007 at 06:20:38AM -0500, Milton Miller wrote:
If CONFIG_PCMCIA=m then your notifier is not registered. The modprobe
of your of_driver loads ds.ko, registers the bus, then registers your
driver. When the socket driver tries to dma, the BUG in dma_64 for no
archdata.dma_ops triggers.
It seems like we need
(1) a notifier that a bus is registered, run before allowing any
devices, so that platforms can register bus notifiers by bus name
before the devices and drivers are registered.
(2) a powerpc64 generic pcmcia bus notifier that copys the dma ops from
the parent socket.
(3) something to set the dma_ops to direct_dma_ops on the of device.
If we don't want (3) to be in the driver (as Christoph previosly
mentioned), then it needs to be a seperate bus that reuses the of
matching. This would be similar to how ibmebus is setup. If I
remember the discussion, ibmebus is to provide the alternate dma ops
and steals match etc code from the of_platform bus type.
Oh, is this why you have depends on PCMCIA=y ?
From: Olof Johansson <hidden> Date: 2007-07-05 14:38:38
Driver for the CompactFlash slot on the PA Semi Electra eval board. It's
a simple device sitting on localbus, with interrupts and detect/voltage
control over GPIO.
The driver is implemented as an of_platform driver, and adds localbus
as a bus being probed by the of_platform framework.
Signed-off-by: Olof Johansson <redacted>
---
On Mon, Jun 25, 2007 at 03:43:41PM -0500, olof wrote:
The ifdef is needed since for CONFIG_PCMCIA=n builds, the bus notifier
isn't available. I wanted to do the bus notifier registration explicitly
before the of_platform bus probe to avoid later surprises due to reordered
initcalls in case it was split up in it's own initcall.
I could add the code under ifdef as well, but it didn't seem too
critical. Once the second major board comes along I'll probably move it
out to a per-board file, there's no real need for it just yet.
Alright, turns out I still need to declare the extern bus type, which would mean
two #ifdefs in one function. Moving it out instead.
I've addressed Milton's comments as well.
Who's maintaining PCMCIA? MAINTAINERS only lists a mailing list, no person. Seems
weird for a component that's marked as maintained.
Index: mainline/drivers/pcmcia/Kconfig
===================================================================
@@ -0,0 +1,374 @@+/*+*Copyright(C)2007PASemi,Inc+*+*Maintainedby:OlofJohansson<olof@lixom.net>+*+*Basedondrivers/pcmcia/omap_cf.c+*+*Thisprogramisfreesoftware;youcanredistributeitand/ormodify+*itunderthetermsoftheGNUGeneralPublicLicenseaspublishedby+*theFreeSoftwareFoundation;eitherversion2oftheLicense,or+*(atyouroption)anylaterversion.+*+*Thisprogramisdistributedinthehopethatitwillbeuseful,+*butWITHOUTANYWARRANTY;withouteventheimpliedwarrantyof+*MERCHANTABILITYorFITNESSFORAPARTICULARPURPOSE.Seethe+*GNUGeneralPublicLicenseformoredetails.+*+*YoushouldhavereceivedacopyoftheGNUGeneralPublicLicense+*alongwiththisprogram;ifnot,writetotheFreeSoftware+*Foundation,Inc.,59TemplePlace,Suite330,Boston,MA02111-1307USA+*/++#include<linux/module.h>+#include<linux/kernel.h>+#include<linux/sched.h>+#include<linux/platform_device.h>+#include<linux/errno.h>+#include<linux/init.h>+#include<linux/delay.h>+#include<linux/interrupt.h>++#include<pcmcia/ss.h>+#include<asm/of_platform.h>++staticconstchardriver_name[]="electra-cf";++structelectra_cf_socket{+structpcmcia_socketsocket;++structtimer_listtimer;+unsignedpresent:1;+unsignedactive:1;++structof_device*ofdev;+unsignedlongmem_phys;+void__iomem*mem_base;+unsignedlongmem_size;+void__iomem*io_virt;+unsignedintio_base;+unsignedintio_size;+u_intirq;+structresourceiomem;+void__iomem*gpio_base;+intgpio_detect;+intgpio_vsense;+intgpio_3v;+intgpio_5v;+};++#define POLL_INTERVAL (2 * HZ)+++staticintelectra_cf_present(structelectra_cf_socket*cf)+{+unsignedintgpio;++gpio=in_le32(cf->gpio_base+0x40);+return!(gpio&(1<<cf->gpio_detect));+}++staticintelectra_cf_ss_init(structpcmcia_socket*s)+{+return0;+}++/* the timer is primarily to kick this socket's pccardd */+staticvoidelectra_cf_timer(unsignedlong_cf)+{+structelectra_cf_socket*cf=(void*)_cf;+intpresent=electra_cf_present(cf);++if(present!=cf->present){+cf->present=present;+pcmcia_parse_events(&cf->socket,SS_DETECT);+}++if(cf->active)+mod_timer(&cf->timer,jiffies+POLL_INTERVAL);+}++staticirqreturn_telectra_cf_irq(intirq,void*_cf)+{+electra_cf_timer((unsignedlong)_cf);+returnIRQ_HANDLED;+}++staticintelectra_cf_get_status(structpcmcia_socket*s,u_int*sp)+{+structelectra_cf_socket*cf;++if(!sp)+return-EINVAL;++cf=container_of(s,structelectra_cf_socket,socket);++/* NOTE CF is always 3VCARD */+if(electra_cf_present(cf)){+structelectra_cf_socket*cf;++*sp=SS_READY|SS_DETECT|SS_POWERON|SS_3VCARD;+cf=container_of(s,structelectra_cf_socket,socket);+s->pci_irq=cf->irq;+}else+*sp=0;+return0;+}++staticintelectra_cf_set_socket(structpcmcia_socket*sock,+structsocket_state_t*s)+{+unsignedintgpio;+unsignedintvcc;+structelectra_cf_socket*cf;++cf=container_of(sock,structelectra_cf_socket,socket);++/* "reset" means no power in our case */+vcc=(s->flags&SS_RESET)?0:s->Vcc;++switch(vcc){+case0:+gpio=0;+break;+case33:+gpio=(1<<cf->gpio_3v);+break;+default:+/* CF is 3.3V only */+return-EINVAL;+}++gpio|=1<<(cf->gpio_3v+16);/* enwr */+gpio|=1<<(cf->gpio_5v+16);/* enwr */+out_le32(cf->gpio_base+0x90,gpio);++pr_debug("%s: Vcc %d, io_irq %d, flags %04x csc %04x\n",+driver_name,s->Vcc,s->io_irq,s->flags,s->csc_mask);++return0;+}++staticintelectra_cf_set_io_map(structpcmcia_socket*s,+structpccard_io_map*io)+{+return0;+}++staticintelectra_cf_set_mem_map(structpcmcia_socket*s,+structpccard_mem_map*map)+{+structelectra_cf_socket*cf;++if(map->card_start)+return-EINVAL;+cf=container_of(s,structelectra_cf_socket,socket);+map->static_start=cf->mem_phys;+map->flags&=MAP_ACTIVE|MAP_ATTRIB;+if(!(map->flags&MAP_ATTRIB))+map->static_start+=0x800;+return0;+}++staticstructpccard_operationselectra_cf_ops={+.init=electra_cf_ss_init,+.get_status=electra_cf_get_status,+.set_socket=electra_cf_set_socket,+.set_io_map=electra_cf_set_io_map,+.set_mem_map=electra_cf_set_mem_map,+};++staticint__devinitelectra_cf_probe(structof_device*ofdev,+conststructof_device_id*match)+{+structdevice*device=&ofdev->dev;+structdevice_node*np=ofdev->node;+structelectra_cf_socket*cf;+structresourcemem,io;+intstatus;+constunsignedint*prop;+interr;++err=of_address_to_resource(np,0,&mem);+if(err)+return-EINVAL;++err=of_address_to_resource(np,1,&io);+if(err)+return-EINVAL;++cf=kzalloc(sizeof*cf,GFP_KERNEL);+if(!cf)+return-ENOMEM;++init_timer(&cf->timer);+cf->timer.function=electra_cf_timer;+cf->timer.data=(unsignedlong)cf;+cf->irq=NO_IRQ;++cf->ofdev=ofdev;+cf->mem_phys=mem.start;+cf->mem_base=ioremap(mem.start,mem.end-mem.start);+cf->io_size=PAGE_ALIGN(io.end-io.start);++cf->io_virt=reserve_phb_iospace(cf->io_size);++cf->gpio_base=ioremap(0xfc103000,0x1000);+dev_set_drvdata(device,cf);++if(!cf->mem_base||!cf->io_virt||!cf->gpio_base){+dev_err(device,"can't ioremap ranges\n");+status=-ENOMEM;+gotofail1;+}++__ioremap_explicit(io.start,(unsignedlong)cf->io_virt,cf->io_size,+_PAGE_NO_CACHE|_PAGE_GUARDED);++cf->io_base=(unsignedlong)cf->io_virt-VMALLOC_END;++cf->iomem.start=(unsignedlong)cf->mem_base;+cf->iomem.end=(unsignedlong)cf->mem_base+(mem.end-mem.start);+cf->iomem.flags=IORESOURCE_MEM;++cf->irq=irq_of_parse_and_map(np,0);++status=request_irq(cf->irq,electra_cf_irq,IRQF_SHARED,+driver_name,cf);+if(status<0){+dev_err(device,"request_irq failed\n");+gotofail1;+}++cf->socket.pci_irq=cf->irq;++prop=of_get_property(np,"card-detect-gpio",NULL);+if(!prop)+gotofail1;+cf->gpio_detect=*prop;++prop=of_get_property(np,"card-vsense-gpio",NULL);+if(!prop)+gotofail1;+cf->gpio_vsense=*prop;++prop=of_get_property(np,"card-3v-gpio",NULL);+if(!prop)+gotofail1;+cf->gpio_3v=*prop;++prop=of_get_property(np,"card-5v-gpio",NULL);+if(!prop)+gotofail1;+cf->gpio_5v=*prop;++cf->socket.io_offset=cf->io_base;++/* reserve chip-select regions */+if(!request_mem_region(mem.start,mem.end+1-mem.start,+driver_name)){+status=-ENXIO;+dev_err(device,"Can't claim memory region\n");+gotofail1;+}++if(!request_region(cf->io_base,cf->io_size,driver_name)){+status=-ENXIO;+dev_err(device,"Can't claim I/O region\n");+gotofail2;+}++cf->socket.owner=THIS_MODULE;+cf->socket.dev.parent=&ofdev->dev;+cf->socket.ops=&electra_cf_ops;+cf->socket.resource_ops=&pccard_static_ops;+cf->socket.features=SS_CAP_PCCARD|SS_CAP_STATIC_MAP|+SS_CAP_MEM_ALIGN;+cf->socket.map_size=0x800;++status=pcmcia_register_socket(&cf->socket);+if(status<0){+dev_err(device,"pcmcia_register_socket failed\n");+gotofail3;+}++dev_info(device,"at mem 0x%lx io 0x%lx irq %d\n",+mem.start,io.start,cf->irq);++cf->active=1;+electra_cf_timer((unsignedlong)cf);+return0;++fail3:+release_mem_region(io.start,io.end+1-io.start);+fail2:+release_mem_region(mem.start,mem.end+1-mem.start);+fail1:+if(cf->irq!=NO_IRQ)+free_irq(cf->irq,cf);++/* XXX No way to undo the ioremap_explicit at this time */+if(cf->mem_base)+iounmap(cf->mem_base);+if(cf->gpio_base)+iounmap(cf->gpio_base);+device_init_wakeup(&ofdev->dev,0);+kfree(cf);+returnstatus;++}++staticint__devexitelectra_cf_remove(structof_device*ofdev)+{+structdevice*device=&ofdev->dev;+structelectra_cf_socket*cf;++cf=dev_get_drvdata(device);++cf->active=0;+pcmcia_unregister_socket(&cf->socket);+free_irq(cf->irq,cf);+del_timer_sync(&cf->timer);++iounmap(cf->mem_base);+iounmap(cf->gpio_base);+release_mem_region(cf->mem_phys,cf->mem_size);+release_region(cf->io_base,cf->io_size);++kfree(cf);++return0;+}++staticstructof_device_idelectra_cf_match[]=+{+{+.compatible="electra-cf",+},+{},+};++staticstructof_platform_driverelectra_cf_driver=+{+.name=(char*)driver_name,+.match_table=electra_cf_match,+.probe=electra_cf_probe,+.remove=electra_cf_remove,+};++staticint__initelectra_cf_init(void)+{+returnof_register_platform_driver(&electra_cf_driver);+}+module_init(electra_cf_init);++staticvoid__exitelectra_cf_exit(void)+{+of_unregister_platform_driver(&electra_cf_driver);+}+module_exit(electra_cf_exit);++MODULE_LICENSE("GPL");+MODULE_AUTHOR("Olof Johansson <olof@lixom.net>");+MODULE_DESCRIPTION("PA Semi Electra CF driver");+
@@ -204,7 +208,57 @@ static void __init pas_init_early(void)iommu_init_early_pasemi();}+#ifdef CONFIG_PCMCIA+staticintpcmcia_notify(structnotifier_block*nb,unsignedlongaction,+void*data)+{+structdevice*dev=data;+structdevice*parent;+structpcmcia_device*pdev=to_pcmcia_dev(dev);++/* We are only intereted in device addition */+if(action!=BUS_NOTIFY_ADD_DEVICE)+return0;++parent=pdev->socket->dev.parent;++/* We know electra_cf devices will always have of_node set, since+*electra_cfisanof_platformdriver.+*/+if(!parent->archdata.of_node)+return0;++if(!of_device_is_compatible(parent->archdata.of_node,"electra-cf"))+return0;++/* We use the direct ops for localbus */+dev->archdata.dma_ops=&dma_direct_ops;++return0;+}++staticstructnotifier_blockpcmcia_notifier={+.notifier_call=pcmcia_notify,+};++staticinlinevoidpasemi_pcmcia_init(void)+{+externstructbus_typepcmcia_bus_type;++bus_register_notifier(&pcmcia_bus_type,&pcmcia_notifier);+}++#else++staticinlinevoidpasemi_pcmcia_init(void)+{+}++#endif++staticstructof_device_idpasemi_bus_ids[]={+{.type="localbus",},{.type="sdc",},{},};
@@ -214,6 +268,8 @@ static int __init pasemi_publish_devicesif(!machine_is(pasemi))return0;+pasemi_pcmcia_init();+/* Publish OF platform devices for SDC and other non-PCI devices */of_platform_bus_probe(NULL,pasemi_bus_ids,NULL);
Driver for the CompactFlash slot on the PA Semi Electra eval board. It's
a simple device sitting on localbus, with interrupts and detect/voltage
control over GPIO.
The driver is implemented as an of_platform driver, and adds localbus
as a bus being probed by the of_platform framework.
Signed-off-by: Olof Johansson <redacted>
---
On Mon, Jun 25, 2007 at 03:43:41PM -0500, olof wrote:
quoted
The ifdef is needed since for CONFIG_PCMCIA=n builds, the bus notifier
isn't available. I wanted to do the bus notifier registration explicitly
before the of_platform bus probe to avoid later surprises due to reordered
initcalls in case it was split up in it's own initcall.
I could add the code under ifdef as well, but it didn't seem too
critical. Once the second major board comes along I'll probably move it
out to a per-board file, there's no real need for it just yet.
Alright, turns out I still need to declare the extern bus type, which would mean
two #ifdefs in one function. Moving it out instead.
I've addressed Milton's comments as well.
Who's maintaining PCMCIA? MAINTAINERS only lists a mailing list, no person. Seems
weird for a component that's marked as maintained.
Dominik Brodowski. He's having a bit of downtime at present (exams, I
think). He expects to return. Meanwhile, cc'ing me usually has some
effect.
I never know who maintains random-scruffy-ppc code like this. From a peek
in the git-whatchanged output, it appears to be yourself.
Have a few little fixies:
From: Olof Johansson <hidden> Date: 2007-08-31 03:37:10
Fix build of electra_cf, since the IO space setup interfaces were
changed when BenH rewrote it.
Also clean it up a bit, add 5V support, make it unloadable, remove some
dead variables, etc.
Signed-off-by: Olof Johansson <redacted>
---
Andrew,
I did this as an incremental patch that you can just merge into the base
one that's already in -mm, but I could merge and resubmit the base patch
instead if you prefer.
(The base patch is
pcmcia-compactflash-driver-for-pa-semi-electra-boards.patch)
-Olof
Index: linux-2.6/drivers/pcmcia/electra_cf.c
===================================================================
@@ -105,10 +106,8 @@ static int electra_cf_get_status(struct /* NOTE CF is always 3VCARD */if(electra_cf_present(cf)){-structelectra_cf_socket*cf;-*sp=SS_READY|SS_DETECT|SS_POWERON|SS_3VCARD;-cf=container_of(s,structelectra_cf_socket,socket);+s->pci_irq=cf->irq;}else*sp=0;
@@ -134,8 +133,10 @@ static int electra_cf_set_socket(struct case33:gpio=(1<<cf->gpio_3v);break;+case5:+gpio=(1<<cf->gpio_5v);+break;default:-/* CF is 3.3V only */return-EINVAL;}
@@ -188,6 +189,7 @@ static int __devinit electra_cf_probe(stintstatus;constunsignedint*prop;interr;+structvm_struct*area;err=of_address_to_resource(np,0,&mem);if(err)
@@ -206,22 +208,27 @@ static int __devinit electra_cf_probe(stcf->ofdev=ofdev;cf->mem_phys=mem.start;-cf->mem_base=ioremap(mem.start,mem.end-mem.start);+cf->mem_size=PAGE_ALIGN(mem.end-mem.start);+cf->mem_base=ioremap(cf->mem_phys,cf->mem_size);cf->io_size=PAGE_ALIGN(io.end-io.start);-cf->io_virt=reserve_phb_iospace(cf->io_size);+area=__get_vm_area(cf->io_size,0,PHB_IO_BASE,PHB_IO_END);+if(area==NULL)+return-ENOMEM;++cf->io_virt=(void__iomem*)(area->addr);cf->gpio_base=ioremap(0xfc103000,0x1000);dev_set_drvdata(device,cf);-if(!cf->mem_base||!cf->io_virt||!cf->gpio_base){+if(!cf->mem_base||!cf->io_virt||!cf->gpio_base||+(__ioremap_at(io.start,cf->io_virt,cf->io_size,+_PAGE_NO_CACHE|_PAGE_GUARDED)==NULL)){dev_err(device,"can't ioremap ranges\n");status=-ENOMEM;gotofail1;}-__ioremap_explicit(io.start,(unsignedlong)cf->io_virt,cf->io_size,-_PAGE_NO_CACHE|_PAGE_GUARDED);cf->io_base=(unsignedlong)cf->io_virt-VMALLOC_END;
@@ -263,8 +270,7 @@ static int __devinit electra_cf_probe(stcf->socket.io_offset=cf->io_base;/* reserve chip-select regions */-if(!request_mem_region(mem.start,mem.end+1-mem.start,-driver_name)){+if(!request_mem_region(cf->mem_phys,cf->mem_size,driver_name)){status=-ENXIO;dev_err(device,"Can't claim memory region\n");gotofail1;
@@ -291,21 +297,22 @@ static int __devinit electra_cf_probe(st}dev_info(device,"at mem 0x%lx io 0x%lx irq %d\n",-mem.start,io.start,cf->irq);+cf->mem_phys,io.start,cf->irq);cf->active=1;electra_cf_timer((unsignedlong)cf);return0;fail3:-release_mem_region(io.start,io.end+1-io.start);+release_region(cf->io_base,cf->io_size);fail2:-release_mem_region(mem.start,mem.end+1-mem.start);+release_mem_region(cf->mem_phys,cf->mem_size);fail1:if(cf->irq!=NO_IRQ)free_irq(cf->irq,cf);-/* XXX No way to undo the ioremap_explicit at this time */+if(cf->io_virt)+__iounmap_at(cf->io_virt,cf->io_size);if(cf->mem_base)iounmap(cf->mem_base);if(cf->gpio_base)
@@ -328,6 +335,7 @@ static int __devexit electra_cf_remove(sfree_irq(cf->irq,cf);del_timer_sync(&cf->timer);+__iounmap_at(cf->io_virt,cf->io_size);iounmap(cf->mem_base);iounmap(cf->gpio_base);release_mem_region(cf->mem_phys,cf->mem_size);