s3cmci driver updates for next kernel merge
Subsystems (3)
cover s3cmci driver updates for next kernel merge
Mostly updates to add SDIO interrupt support as well as driver cleanups and DMA fixes / improvements.
PATCH 1/9 s3cmci: Use resource_size() instead of local macro
Replace the local definition RESSIZE() with the standard resource_size() call for getting the size of a struct resource.
--- drivers/mmc/host/s3cmci.c | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-)
drivers/mmc/host/s3cmci.c +4 -6
@@ -58,8 +58,6 @@ static const int dbgmap_debug = dbg_errdev_dbg (&host->pdev->dev, args); \ }while (0 ) -#define RESSIZE (ressource) (((ressource)->end - (ressource)->start)+1) - static struct s3c2410_dma_client s3cmci_dma_client = { .name ="s3c-mci" , };
@@ -1298,7 +1296,7 @@ static int __devinit s3cmci_probe(struct } host->mem =request_mem_region (host->mem->start, -RESSIZE (host->mem), pdev->name); +resource_size (host->mem), pdev->name);if (!host->mem) {dev_err (&pdev->dev,"failed to request io memory region.\n" );
@@ -1306,7 +1304,7 @@ static int __devinit s3cmci_probe(struct goto probe_free_host; } - host->base =ioremap (host->mem->start,RESSIZE (host->mem)); + host->base =ioremap (host->mem->start,resource_size (host->mem));if (!host->base) {dev_err (&pdev->dev,"failed to ioremap() io memory region.\n" ); ret = -EINVAL ;
@@ -1433,7 +1431,7 @@ static int __devinit s3cmci_probe(structiounmap (host->base); probe_free_mem_region: -release_mem_region (host->mem->start,RESSIZE (host->mem)); +release_mem_region (host->mem->start,resource_size (host->mem)); probe_free_host:mmc_free_host (mmc);
@@ -1469,7 +1467,7 @@ static int __devexit s3cmci_remove(strucfree_irq (host->irq, host);iounmap (host->base); -release_mem_region (host->mem->start,RESSIZE (host->mem)); +release_mem_region (host->mem->start,resource_size (host->mem));mmc_free_host (mmc);return 0 ;
PATCH 2/9 s3cmci: update probe to use new platform id list.
In reply to Ben DooksUse the platform id list to match the three different versions of the hardware block that this driver supports.
This will change the prefix of the console messages produced by this driver to be prefixed by s3c-mci instead of the hardware block name, such as s3c2440-mci.
--- drivers/mmc/host/s3cmci.c | 71 +++++++++++++++------------------------------- 1 file changed, 24 insertions(+), 47 deletions(-)
drivers/mmc/host/s3cmci.c +24 -47
@@ -1244,11 +1244,14 @@ static inline void s3cmci_cpufreq_deregi }#endif -static int __devinit s3cmci_probe (struct platform_device *pdev,int is2440) +static int __devinit s3cmci_probe (struct platform_device *pdev) {struct s3cmci_host *host;struct mmc_host *mmc;int ret; +int is2440; + + is2440 =platform_get_device_id (pdev)->driver_data; mmc =mmc_alloc_host (sizeof (struct s3cmci_host ), &pdev->dev);if (!mmc) {
@@ -1473,20 +1476,22 @@ static int __devexit s3cmci_remove(strucreturn 0 ; } -static int __devinit s3cmci_2410_probe (struct platform_device *dev) -{ -return s3cmci_probe (dev,0 ); -} +static struct platform_device_id s3cmci_driver_ids[] = { + { + .name ="s3c2410-sdi" , + .driver_data =0 , + }, { + .name ="s3c2412-sdi" , + .driver_data =1 , + }, { + .name ="s3c2440-sdi" , + .driver_data =1 , + }, + { } +}; -static int __devinit s3cmci_2412_probe (struct platform_device *dev) -{ -return s3cmci_probe (dev,1 ); -} +MODULE_DEVICE_TABLE (platform, s3cmci_driver_ids); -static int __devinit s3cmci_2440_probe (struct platform_device *dev) -{ -return s3cmci_probe (dev,1 ); -}#ifdef CONFIG_PM
@@ -1510,50 +1515,25 @@ static int s3cmci_resume(struct platform#endif /* CONFIG_PM */ -static struct platform_driver s3cmci_2410_driver = { - .driver.name ="s3c2410-sdi" , - .driver.owner =THIS_MODULE , - .probe = s3cmci_2410_probe, - .remove =__devexit_p (s3cmci_remove), - .shutdown = s3cmci_shutdown, - .suspend = s3cmci_suspend, - .resume = s3cmci_resume, -}; - -static struct platform_driver s3cmci_2412_driver = { - .driver.name ="s3c2412-sdi" , - .driver.owner =THIS_MODULE , - .probe = s3cmci_2412_probe, - .remove =__devexit_p (s3cmci_remove), - .shutdown = s3cmci_shutdown, - .suspend = s3cmci_suspend, - .resume = s3cmci_resume, -}; - -static struct platform_driver s3cmci_2440_driver = { - .driver.name ="s3c2440-sdi" , +static struct platform_driver s3cmci_driver = { + .driver.name ="s3c-sdi" , .driver.owner =THIS_MODULE , - .probe = s3cmci_2440_probe, + .id_table = s3cmci_driver_ids, + .probe = s3cmci_probe, .remove =__devexit_p (s3cmci_remove), .shutdown = s3cmci_shutdown, .suspend = s3cmci_suspend, .resume = s3cmci_resume, }; -static int __init s3cmci_init (void ) { -platform_driver_register (&s3cmci_2410_driver); -platform_driver_register (&s3cmci_2412_driver); -platform_driver_register (&s3cmci_2440_driver); -return 0 ; +return platform_driver_register (&s3cmci_driver); } static void __exit s3cmci_exit(void) { -platform_driver_unregister (&s3cmci_2410_driver); -platform_driver_unregister (&s3cmci_2412_driver); -platform_driver_unregister (&s3cmci_2440_driver); +platform_driver_unregister (&s3cmci_driver); }module_init (s3cmci_init);
@@ -1562,6 +1542,3 @@ module_exit(s3cmci_exit);MODULE_DESCRIPTION ("Samsung S3C MMC/SD Card Interface driver" );MODULE_LICENSE ("GPL v2" );MODULE_AUTHOR ("Thomas Kleffel <[email protected]>, Ben Dooks <[email protected]>" ); -MODULE_ALIAS ("platform:s3c2410-sdi" ); -MODULE_ALIAS ("platform:s3c2412-sdi" ); -MODULE_ALIAS ("platform:s3c2440-sdi" );
PATCH 3/9 s3cmci: Change GPIO to gpiolib from S3C24XX specific calls
In reply to Ben DooksMove to using gpiolib to access the card detect and write protect GPIO lines instead of using the platform speicifc s3c2410_gpio calls.
Also ensure that the card lines are claimed the same way to avoid overlap with any other drivers.
--- drivers/mmc/host/s3cmci.c | 75 ++++++++++++++++++++++++++++++++++++++-------- 1 file changed, 63 insertions(+), 12 deletions(-)
drivers/mmc/host/s3cmci.c +63 -12
@@ -1047,7 +1047,7 @@ static int s3cmci_card_present(struct mmif (pdata->gpio_detect ==0 )return -ENOSYS ; - ret =s3c2410_gpio_getpin (pdata->gpio_detect) ?0 :1 ; + ret =gpio_get_value (pdata->gpio_detect) ?0 :1 ;return ret ^ pdata->detect_invert; }
@@ -1119,8 +1119,7 @@ static void s3cmci_set_ios(struct mmc_hocase MMC_POWER_OFF :default : -s3c2410_gpio_setpin (S3C2410_GPE5 ,0 ); -s3c2410_gpio_cfgpin (S3C2410_GPE5 ,S3C2410_GPIO_OUTPUT ); +gpio_direction_output (S3C2410_GPE5 ,0 );if (host->is2440) mci_con |=S3C2440_SDICON_SDRESET ;
@@ -1244,12 +1243,14 @@ static inline void s3cmci_cpufreq_deregi }#endif +static int __devinit s3cmci_probe (struct platform_device *pdev) {struct s3cmci_host *host;struct mmc_host *mmc;int ret;int is2440; +int i; is2440 =platform_get_device_id (pdev)->driver_data;
@@ -1259,6 +1260,18 @@ static int __devinit s3cmci_probe(struct goto probe_out; } +for (i =S3C2410_GPE (5 ); i <=S3C2410_GPE (10 ); i++) { + ret =gpio_request (i,dev_name (&pdev->dev)); +if (ret) { +dev_err (&pdev->dev,"failed to get gpio %d\n" , i); + +for (i--; i >=S3C2410_GPE (5 ); i--) +gpio_free (i); + + goto probe_free_host; + } + } + host =mmc_priv (mmc); host->mmc = mmc; host->pdev = pdev;
@@ -1295,7 +1308,7 @@ static int __devinit s3cmci_probe(struct "failed to get io memory region resouce.\n"); ret = -ENOENT ; - goto probe_free_host; + goto probe_free_gpio; } host->mem = request_mem_region(host->mem->start,
@@ -1304,7 +1317,7 @@ static int __devinit s3cmci_probe(structif (!host->mem) {dev_err (&pdev->dev,"failed to request io memory region.\n" ); ret = -ENOENT ; - goto probe_free_host; + goto probe_free_gpio; } host->base =ioremap (host->mem->start,resource_size (host->mem));
@@ -1333,6 +1346,14 @@ static int __devinit s3cmci_probe(structdisable_irq (host->irq); +if (host->pdata->gpio_detect) { + ret =gpio_request (host->pdata->gpio_detect,"s3cmci detect" ); +if (ret) { +dev_err (&pdev->dev,"failed to get detect gpio\n" ); + goto probe_free_irq; + } + } + host->irq_cd =s3c2410_gpio_getirq (host->pdata->gpio_detect);if (host->irq_cd >=0 ) {
@@ -1341,22 +1362,27 @@ static int __devinit s3cmci_probe(struct DRIVER_NAME, host)) {dev_err (&pdev->dev,"can't get card detect irq.\n" ); ret = -ENOENT ; - goto probe_free_irq; + goto probe_free_gpio_cd; } }else {dev_warn (&pdev->dev,"host detect has no irq available\n" ); -s3c2410_gpio_cfgpin (host->pdata->gpio_detect, -S3C2410_GPIO_INPUT ); +gpio_direction_input (host->pdata->gpio_detect); } -if (host->pdata->gpio_wprotect) -s3c2410_gpio_cfgpin (host->pdata->gpio_wprotect, -S3C2410_GPIO_INPUT ); +if (host->pdata->gpio_wprotect) { + ret =gpio_request (host->pdata->gpio_wprotect,"s3cmci wp" ); +if (ret) { +dev_err (&pdev->dev,"failed to get writeprotect\n" ); + goto probe_free_irq_cd; + } + +gpio_direction_input (host->pdata->gpio_wprotect); + }if (s3c2410_dma_request (S3CMCI_DMA , &s3cmci_dma_client,NULL ) <0 ) {dev_err (&pdev->dev,"unable to get DMA channel.\n" ); ret = -EBUSY ; - goto probe_free_irq_cd; + goto probe_free_gpio_wp; } host->clk =clk_get (&pdev->dev,"sdi" );
@@ -1423,6 +1449,14 @@ static int __devinit s3cmci_probe(struct clk_free:clk_put (host->clk); + probe_free_gpio_wp: +if (host->pdata->gpio_wprotect) +gpio_free (host->pdata->gpio_wprotect); + + probe_free_gpio_cd: +if (host->pdata->gpio_detect) +gpio_free (host->pdata->gpio_detect); + probe_free_irq_cd:if (host->irq_cd >=0 )free_irq (host->irq_cd, host);
@@ -1436,8 +1470,13 @@ static int __devinit s3cmci_probe(struct probe_free_mem_region:release_mem_region (host->mem->start,resource_size (host->mem)); + probe_free_gpio: +for (i =S3C2410_GPE (5 ); i <=S3C2410_GPE (10 ); i++) +gpio_free (i); + probe_free_host:mmc_free_host (mmc); + probe_out:return ret; }
@@ -1459,6 +1498,8 @@ static int __devexit s3cmci_remove(struc {struct mmc_host *mmc =platform_get_drvdata (pdev);struct s3cmci_host *host =mmc_priv (mmc); +struct s3c24xx_mci_pdata *pd = host->pdata; +int i;s3cmci_shutdown (pdev);
@@ -1469,6 +1510,16 @@ static int __devexit s3cmci_remove(strucfree_irq (host->irq, host); +if (pd->gpio_wprotect) +gpio_free (pd->gpio_wprotect); + +if (pd->gpio_detect) +gpio_free (pd->gpio_detect); + +for (i =S3C2410_GPE (5 ); i <=S3C2410_GPE (10 ); i++) +gpio_free (i); + +iounmap (host->base);release_mem_region (host->mem->start,resource_size (host->mem));
PATCH 4/9 s3cmci: Change to use dev_pm_ops
In reply to Ben DooksMove to using dev_pm_ops for suspend and resume.
--- drivers/mmc/host/s3cmci.c | 29 ++++++++++++++++++----------- 1 file changed, 18 insertions(+), 11 deletions(-)
drivers/mmc/host/s3cmci.c +18 -11
@@ -1546,35 +1546,42 @@ MODULE_DEVICE_TABLE(platform, s3cmci_dri#ifdef CONFIG_PM -static int s3cmci_suspend (struct platform_device *dev,pm_message_t state) +static int s3cmci_suspend (struct device *dev) { -struct mmc_host *mmc =platform_get_drvdata (dev); +struct mmc_host *mmc =platform_get_drvdata (to_platform_device (dev)); +struct pm_message event = {PM_EVENT_SUSPEND }; -return mmc_suspend_host (mmc, state); +return mmc_suspend_host (mmc, event); } -static int s3cmci_resume (struct platform_device *dev) +static int s3cmci_resume (struct device *dev) { -struct mmc_host *mmc =platform_get_drvdata (dev); +struct mmc_host *mmc =platform_get_drvdata (to_platform_device (dev));return mmc_resume_host (mmc); } +static struct dev_pm_ops s3cmci_pm = { + .suspend = s3cmci_suspend, + .resume = s3cmci_resume, +}; + +#define s3cmci_pm_ops &s3cmci_pm#else /* CONFIG_PM */ -#define s3cmci_suspend NULL -#define s3cmci_resume NULL +#define s3cmci_pm_ops NULL#endif /* CONFIG_PM */ static struct platform_driver s3cmci_driver = { - .driver.name ="s3c-sdi" , - .driver.owner =THIS_MODULE , + .driver = { + .name ="s3c-sdi" , + .owner =THIS_MODULE , + .pm = s3cmci_pm_ops, + }, .id_table = s3cmci_driver_ids, .probe = s3cmci_probe, .remove =__devexit_p (s3cmci_remove), .shutdown = s3cmci_shutdown, - .suspend = s3cmci_suspend, - .resume = s3cmci_resume, };static int __init s3cmci_init (void )
PATCH 5/9 s3cmci: Fix direct write to interrupt mask
In reply to Ben DooksThe clear_imask() call should be used to clear the interrupt mask register, as it may end up clearing the SDIO interrupt bit if this is enabled.
Change all writes of zero to SDIIMSK register to use clear_imask() ready for the SDIO updates.
---
drivers/mmc/host/s3cmci.c +3 -3
@@ -681,9 +681,9 @@ out: fail_request: host->mrq->data->error = -EINVAL ; host->complete_what =COMPLETION_FINALIZE ; -writel (0 , host->base + host->sdiimsk); - goto out; +clear_imask (host); + goto out; }static void finalize_request (struct s3cmci_host *host)
@@ -726,7 +726,7 @@ static void finalize_request(struct s3cmwritel (0 , host->base +S3C2410_SDICMDARG );writel (S3C2410_SDIDCON_STOP , host->base +S3C2410_SDIDCON );writel (0 , host->base +S3C2410_SDICMDCON ); -writel (0 , host->base + host->sdiimsk); +clear_imask (host);if (cmd->data && cmd->error) cmd->data->error = cmd->error;
PATCH 6/9 s3cmci: Add debugfs support for examining driver and hardware state.
In reply to Ben DooksExport driver state and hardware register state via debugfs entries created under a directory formed from dev_name() on the probed device when CONFIG_DEBUG_FS is set.
--- drivers/mmc/host/s3cmci.c | 126 ++++++++++++++++++++++++++++++++++++++++++++++ drivers/mmc/host/s3cmci.h | 6 ++ 2 files changed, 132 insertions(+)
drivers/mmc/host/s3cmci.c +126 -0
@@ -17,6 +17,8 @@#include <linux/mmc/host.h> #include <linux/platform_device.h> #include <linux/cpufreq.h> +#include <linux/debugfs.h> +#include <linux/seq_file.h> #include <linux/gpio.h> #include <linux/irq.h> #include <linux/io.h>
@@ -1244,6 +1246,127 @@ static inline void s3cmci_cpufreq_deregi#endif +#ifdef CONFIG_DEBUG_FS + +static int s3cmci_state_show (struct seq_file *seq,void *v) +{ +struct s3cmci_host *host = seq->private; + +seq_printf (seq,"Register base = 0x%08x\n" , (u32 )host->base); +seq_printf (seq,"Clock rate = %ld\n" , host->clk_rate); +seq_printf (seq,"Prescale = %d\n" , host->prescaler); +seq_printf (seq,"is2440 = %d\n" , host->is2440); +seq_printf (seq,"IRQ = %d\n" , host->irq); +seq_printf (seq,"CD IRQ = %d\n" , host->irq_cd); +seq_printf (seq,"Do DMA = %d\n" , host->dodma); +seq_printf (seq,"SDIIMSK at %d\n" , host->sdiimsk); +seq_printf (seq,"SDIDATA at %d\n" , host->sdidata); + +return 0 ; +} + +static int s3cmci_state_open (struct inode *inode,struct file *file) +{ +return single_open (file, s3cmci_state_show, inode->i_private); +} + +static const struct file_operations s3cmci_fops_state = { + .owner =THIS_MODULE , + .open = s3cmci_state_open, + .read = seq_read, + .llseek = seq_lseek, + .release = single_release, +}; + +#define DBG_REG (_r) { .addr = S3C2410_SDI##_r, .name = #_r } + +struct s3cmci_reg { +unsigned short addr; +unsigned char *name; +} debug_regs[] = { +DBG_REG (CON ), +DBG_REG (PRE ), +DBG_REG (CMDARG ), +DBG_REG (CMDCON ), +DBG_REG (CMDSTAT ), +DBG_REG (RSP0 ), +DBG_REG (RSP1 ), +DBG_REG (RSP2 ), +DBG_REG (RSP3 ), +DBG_REG (TIMER ), +DBG_REG (BSIZE ), +DBG_REG (DCON ), +DBG_REG (DCNT ), +DBG_REG (DSTA ), +DBG_REG (FSTA ), + {} +}; + +static int s3cmci_regs_show (struct seq_file *seq,void *v) +{ +struct s3cmci_host *host = seq->private; +struct s3cmci_reg *rptr = debug_regs; + +for (; rptr->name; rptr++) +seq_printf (seq,"SDI%s\t=0x%08x\n" , rptr->name, +readl (host->base + rptr->addr)); + +seq_printf (seq,"SDIIMSK\t=0x%08x\n" ,readl (host->base + host->sdiimsk)); + +return 0 ; +} + +static int s3cmci_regs_open (struct inode *inode,struct file *file) +{ +return single_open (file, s3cmci_regs_show, inode->i_private); +} + +static const struct file_operations s3cmci_fops_regs = { + .owner =THIS_MODULE , + .open = s3cmci_regs_open, + .read = seq_read, + .llseek = seq_lseek, + .release = single_release, +}; + +static void s3cmci_debugfs_attach (struct s3cmci_host *host) +{ +struct device *dev = &host->pdev->dev; + + host->debug_root =debugfs_create_dir (dev_name (dev),NULL ); +if (IS_ERR (host->debug_root)) { +dev_err (dev,"failed to create debugfs root\n" ); +return ; + } + + host->debug_state =debugfs_create_file ("state" ,0444 , + host->debug_root, host, + &s3cmci_fops_state); + +if (IS_ERR (host->debug_state)) +dev_err (dev,"failed to create debug state file\n" ); + + host->debug_regs =debugfs_create_file ("regs" ,0444 , + host->debug_root, host, + &s3cmci_fops_regs); + +if (IS_ERR (host->debug_regs)) +dev_err (dev,"failed to create debug regs file\n" ); +} + +static void s3cmci_debugfs_remove (struct s3cmci_host *host) +{ +debugfs_remove (host->debug_regs); +debugfs_remove (host->debug_state); +debugfs_remove (host->debug_root); +} + +#else +static inline void s3cmci_debugfs_attach (struct s3cmci_host *host) { } +static inline void s3cmci_debugfs_remove (struct s3cmci_host *host) { } + +#endif /* CONFIG_DEBUG_FS */ +static int __devinit s3cmci_probe (struct platform_device *pdev) {struct s3cmci_host *host;
@@ -1435,6 +1558,8 @@ static int __devinit s3cmci_probe(struct goto free_cpufreq; } +s3cmci_debugfs_attach (host); +platform_set_drvdata (pdev, mmc);dev_info (&pdev->dev,"initialisation done.\n" );
@@ -1489,6 +1614,7 @@ static void s3cmci_shutdown(struct platfif (host->irq_cd >=0 )free_irq (host->irq_cd, host); +s3cmci_debugfs_remove (host);s3cmci_cpufreq_deregister (host);mmc_remove_host (mmc);clk_disable (host->clk);
drivers/mmc/host/s3cmci.h +6 -0
@@ -68,6 +68,12 @@ struct s3cmci_host {unsigned int ccnt, dcnt;struct tasklet_struct pio_tasklet; +#ifdef CONFIG_DEBUG_FS +struct dentry *debug_root; +struct dentry *debug_state; +struct dentry *debug_regs; +#endif +#ifdef CONFIG_CPU_FREQ struct notifier_block freq_transition;#endif
PATCH 7/9 s3cmci: Add SDIO IRQ support
In reply to Ben DooksThe controller supports SDIO IRQ detection so add support for hardware assisted SDIO interrupt detection for the SDIO core. This improves the response time for SDIO interrupts and thus the transfer rate from devices such as the Marvel 8686.
As a note, it does seem that the controller will miss an IRQ than is held asserted, so there are some manual checks to see if the SDIO interrupt is active after a transfer.
Major testing on the S3C2440.
--- drivers/mmc/host/s3cmci.c | 161 +++++++++++++++++++++++++++++++++++++++++++--- drivers/mmc/host/s3cmci.h | 5 + 2 files changed, 156 insertions(+), 10 deletions(-)
drivers/mmc/host/s3cmci.c +151 -10
@@ -190,7 +190,33 @@ static inline u32 disable_imask(struct sstatic inline void clear_imask (struct s3cmci_host *host) { -writel (0 , host->base + host->sdiimsk); +u32 mask =readl (host->base + host->sdiimsk); + +/* preserve the SDIO IRQ mask state */ + mask &=S3C2410_SDIIMSK_SDIOIRQ ; +writel (mask, host->base + host->sdiimsk); +} + +/** +* s3cmci_check_sdio_irq - test whether the SDIO IRQ is being signalled +* @host: The host to check. +* +* Test to see if the SDIO interrupt is being signalled in case the +* controller has failed to re-detect a card interrupt. Read GPE8 and +* see if it is low and if so, signal a SDIO interrupt. +* +* This is currently called if a request is finished (we assume that the +* bus is now idle) and when the SDIO IRQ is enabled in case the IRQ is +* already being indicated. +*/ +static void s3cmci_check_sdio_irq (struct s3cmci_host *host) +{ +if (host->sdio_irqen) { +if (gpio_get_value (S3C2410_GPE (8 )) ==0 ) { +printk (KERN_DEBUG "%s: signalling irq\n" , __func__); +mmc_signal_sdio_irq (host->mmc); + } + } } static inline int get_data_buffer(struct s3cmci_host *host,
@@ -238,6 +264,64 @@ static inline u32 fifo_free(struct s3cmcreturn 63 - fifostat; } +/** +* s3cmci_enable_irq - enable IRQ, after having disabled it. +* @host: The device state. +* @more: True if more IRQs are expected from transfer. +* +* Enable the main IRQ if needed after it has been disabled. +* +* The IRQ can be one of the following states: +* - disabled during IDLE +* - disabled whilst processing data +* - enabled during transfer +* - enabled whilst awaiting SDIO interrupt detection +*/ +static void s3cmci_enable_irq (struct s3cmci_host *host,bool more) +{ +unsigned long flags; +bool enable = false; + +local_irq_save (flags); + + host->irq_enabled = more; + host->irq_disabled = false; + + enable = more | host->sdio_irqen; + +if (host->irq_state != enable) { + host->irq_state = enable; + +if (enable) +enable_irq (host->irq); +else +disable_irq (host->irq); + } + +local_irq_restore (flags); +} + +/** +* +*/ +static void s3cmci_disable_irq (struct s3cmci_host *host,bool transfer) +{ +unsigned long flags; + +local_irq_save (flags); + +//printk(KERN_DEBUG "%s: transfer %d\n", __func__, transfer); + + host->irq_disabled = transfer; + +if (transfer && host->irq_state) { + host->irq_state = false; +disable_irq (host->irq); + } + +local_irq_restore (flags); +} +static void do_pio_read (struct s3cmci_host *host) {int res;
@@ -374,8 +458,7 @@ static void pio_tasklet(unsigned long da {struct s3cmci_host *host = (struct s3cmci_host *) data; - -disable_irq (host->irq); +s3cmci_disable_irq (host, true);if (host->pio_active ==XFER_WRITE )do_pio_write (host);
@@ -395,9 +478,10 @@ static void pio_tasklet(unsigned long da host->mrq->data->error = -EINVAL ; } +s3cmci_enable_irq (host, false);finalize_request (host); }else -enable_irq (host->irq); +s3cmci_enable_irq (host, true); }/*
@@ -432,17 +516,27 @@ static irqreturn_t s3cmci_irq(int irq, vstruct s3cmci_host *host = dev_id;struct mmc_command *cmd;u32 mci_csta, mci_dsta, mci_fsta, mci_dcnt, mci_imsk; -u32 mci_cclear, mci_dclear; +u32 mci_cclear =0 , mci_dclear;unsigned long iflags; + mci_dsta =readl (host->base +S3C2410_SDIDSTA ); + mci_imsk =readl (host->base + host->sdiimsk); + +if (mci_dsta &S3C2410_SDIDSTA_SDIOIRQDETECT ) { +if (mci_imsk &S3C2410_SDIIMSK_SDIOIRQ ) { + mci_dclear =S3C2410_SDIDSTA_SDIOIRQDETECT ; +writel (mci_dclear, host->base +S3C2410_SDIDSTA ); + +mmc_signal_sdio_irq (host->mmc); +return IRQ_HANDLED ; + } + } +spin_lock_irqsave (&host->complete_lock, iflags); mci_csta =readl (host->base +S3C2410_SDICMDSTAT ); - mci_dsta =readl (host->base +S3C2410_SDIDSTA ); mci_dcnt =readl (host->base +S3C2410_SDIDCNT ); mci_fsta =readl (host->base +S3C2410_SDIFSTA ); - mci_imsk =readl (host->base + host->sdiimsk); - mci_cclear =0 ; mci_dclear =0 ; if ((host->complete_what == COMPLETION_NONE) ||
@@ -776,6 +870,8 @@ static void finalize_request(struct s3cm request_done: host->complete_what =COMPLETION_NONE ; host->mrq =NULL ; + +s3cmci_check_sdio_irq (host);mmc_request_done (host->mmc, mrq); }
@@ -1037,7 +1133,7 @@ static void s3cmci_send_request(struct ms3cmci_send_command (host, cmd);/* Enable Interrupt */ -enable_irq (host->irq); +s3cmci_enable_irq (host, true); }static int s3cmci_card_present (struct mmc_host *mmc)
@@ -1178,11 +1274,52 @@ static int s3cmci_get_ro(struct mmc_hostreturn ret; } +static void s3cmci_enable_sdio_irq (struct mmc_host *mmc,int enable) +{ +struct s3cmci_host *host =mmc_priv (mmc); +unsigned long flags; +u32 con; + +local_irq_save (flags); + + con =readl (host->base +S3C2410_SDICON ); + host->sdio_irqen = enable; + +if (enable == host->sdio_irqen) + goto same_state; + +if (enable) { + con |=S3C2410_SDICON_SDIOIRQ ; +enable_imask (host,S3C2410_SDIIMSK_SDIOIRQ ); + +if (!host->irq_state && !host->irq_disabled) { + host->irq_state = true; +enable_irq (host->irq); + } + }else { +disable_imask (host,S3C2410_SDIIMSK_SDIOIRQ ); + con &= ~S3C2410_SDICON_SDIOIRQ ; + +if (!host->irq_enabled && host->irq_state) { +disable_irq_nosync (host->irq); + host->irq_state = false; + } + } + +writel (con, host->base +S3C2410_SDICON ); + + same_state: +local_irq_restore (flags); + +s3cmci_check_sdio_irq (host); +} +static struct mmc_host_ops s3cmci_ops = { .request = s3cmci_request, .set_ios = s3cmci_set_ios, .get_ro = s3cmci_get_ro, .get_cd = s3cmci_card_present, + .enable_sdio_irq = s3cmci_enable_sdio_irq, };static struct s3c24xx_mci_pdata s3cmci_def_pdata = {
@@ -1257,6 +1394,9 @@ static int s3cmci_state_show(struct seq_seq_printf (seq,"Prescale = %d\n" , host->prescaler);seq_printf (seq,"is2440 = %d\n" , host->is2440);seq_printf (seq,"IRQ = %d\n" , host->irq); +seq_printf (seq,"IRQ enabled = %d\n" , host->irq_enabled); +seq_printf (seq,"IRQ disabled = %d\n" , host->irq_disabled); +seq_printf (seq,"IRQ state = %d\n" , host->irq_state);seq_printf (seq,"CD IRQ = %d\n" , host->irq_cd);seq_printf (seq,"Do DMA = %d\n" , host->dodma);seq_printf (seq,"SDIIMSK at %d\n" , host->sdiimsk);
@@ -1468,6 +1608,7 @@ static int __devinit s3cmci_probe(struct* ensure we don't lock the system with un-serviceable requests. */ disable_irq (host->irq); + host->irq_state = false;if (host->pdata->gpio_detect) { ret =gpio_request (host->pdata->gpio_detect,"s3cmci detect" );
@@ -1526,7 +1667,7 @@ static int __devinit s3cmci_probe(struct mmc->ops = &s3cmci_ops; mmc->ocr_avail =MMC_VDD_32_33 |MMC_VDD_33_34 ; - mmc->caps =MMC_CAP_4_BIT_DATA ; + mmc->caps =MMC_CAP_4_BIT_DATA |MMC_CAP_SDIO_IRQ ; mmc->f_min = host->clk_rate / (host->clk_div *256 ); mmc->f_max = host->clk_rate / host->clk_div;
drivers/mmc/host/s3cmci.h +5 -0
@@ -42,6 +42,11 @@ struct s3cmci_host {int dodma;int dmatogo; +bool irq_disabled; +bool irq_enabled; +bool irq_state; +int sdio_irqen; +struct mmc_request *mrq;int cmd_is_stop;
PATCH 8/9 s3cmci: Kconfig selection for PIO/DMA/Both
In reply to Ben DooksAdd a selection for the data transfer mode of the s3cmci driver, allowing for either a configuration or rumtime selection of the use of the DMA or PIO transfer code.
The PIO only mode is 476 bytes smaller than the driver with both methods compiled in.
--- arch/arm/plat-s3c24xx/include/plat/mci.h | 1 drivers/mmc/host/Kconfig | 35 +++++++++++++++++++++++++++ drivers/mmc/host/s3cmci.c | 39 ++++++++++++++++++++++++------- 3 files changed, 67 insertions(+), 8 deletions(-)
drivers/mmc/host/Kconfig +35 -0
@@ -261,6 +261,41 @@ config MMC_S3C If unsure, say N. +choice + prompt "Samsung S3C SD/MMC transfer code" + depends on MMC_S3C + +config MMC_S3C_PIO + bool "Use PIO transfers only" + default y + help + Use PIO to transfer data between memory and the hardware. + + PIO is slower than DMA as it requires CPU instructions to + move the data. This has been the traditional default for + the S3C MCI driver. + +config MMC_S3C_DMA + bool "Use DMA transfers only (EXPERIMENTAL)" + depends on EXPERIMENTAL + help + Use DMA to transfer data between memory and the hardare. + + Currently, the DMA support in this driver seems to not be + working properly and needs to be debugged before this + option is useful. + +config MMC_S3C_PIODMA + bool "Support for both PIO and DMA (EXPERIMENTAL)" + help + Compile both the PIO and DMA transfer routines into the + driver and let the platform select at run-time which one + is best. + + See notes for the DMA option. + +endchoice + config MMC_SDRICOH_CS tristate "MMC/SD driver for Ricoh Bay1Controllers (EXPERIMENTAL)" depends on EXPERIMENTAL && PCI && PCMCIA
drivers/mmc/host/s3cmci.c +31 -8
@@ -164,6 +164,25 @@ static void dbg_dumpregs(struct s3cmci_h#endif /* CONFIG_MMC_DEBUG */ +/** +* s3cmci_host_usedma - return whether the host is using dma or pio +* @host: The host state +* +* Return true if the host is using DMA to transfer data, else false +* to use PIO mode. Will return static data depending on the driver +* configuration. +*/ +static inline bool s3cmci_host_usedma (struct s3cmci_host *host) +{ +#ifdef CONFIG_MMC_S3C_PIO +return false; +#elif defined(CONFIG_MMC_S3C_DMA ) +return true; +#else +return host->dodma; +#endif +} +static inline u32 enable_imask (struct s3cmci_host *host,u32 imask) {u32 newmask;
@@ -560,7 +579,7 @@ static irqreturn_t s3cmci_irq(int irq, v goto irq_out; } -if (!host->dodma) { +if (!s3cmci_host_usedma (host)) {if ((host->pio_active ==XFER_WRITE ) && (mci_fsta &S3C2410_SDIFSTA_TFDET )) {
@@ -796,7 +815,7 @@ static void finalize_request(struct s3cmif (cmd->data && (cmd->error ==0 ) && (cmd->data->error ==0 )) { -if (host->dodma && (!host->dma_complete)) { +if (s3cmci_host_usedma (host) && (!host->dma_complete)) {dbg (host, dbg_dma,"DMA Missing!\n" );return ; }
@@ -848,7 +867,7 @@ static void finalize_request(struct s3cm/* If we had an error while transfering data we flush the * DMA channel and the fifo to clear out any garbage. */ if (mrq->data->error !=0 ) { -if (host->dodma) +if (s3cmci_host_usedma (host))s3c2410_dma_ctrl (host->dma,S3C2410_DMAOP_FLUSH );if (host->is2440) {
@@ -968,7 +987,7 @@ static int s3cmci_setup_data(struct s3cm dcon = data->blocks &S3C2410_SDIDCON_BLKNUM_MASK ; -if (host->dodma) +if (s3cmci_host_usedma (host)) dcon |=S3C2410_SDIDCON_DMAEN ;if (host->bus_width ==MMC_BUS_WIDTH_4 )
@@ -1114,7 +1133,7 @@ static void s3cmci_send_request(struct mreturn ; } -if (host->dodma) +if (s3cmci_host_usedma (host)) res =s3cmci_prepare_dma (host, cmd->data);else res =s3cmci_prepare_pio (host, cmd->data);
@@ -1398,7 +1417,7 @@ static int s3cmci_state_show(struct seq_seq_printf (seq,"IRQ disabled = %d\n" , host->irq_disabled);seq_printf (seq,"IRQ state = %d\n" , host->irq_state);seq_printf (seq,"CD IRQ = %d\n" , host->irq_cd); -seq_printf (seq,"Do DMA = %d\n" , host->dodma); +seq_printf (seq,"Do DMA = %d\n" ,s3cmci_host_usedma (host));seq_printf (seq,"SDIIMSK at %d\n" , host->sdiimsk);seq_printf (seq,"SDIDATA at %d\n" , host->sdidata);
@@ -1559,12 +1578,15 @@ static int __devinit s3cmci_probe(struct host->clk_div =2 ; } - host->dodma =0 ; host->complete_what =COMPLETION_NONE ; host->pio_active =XFER_NONE ; host->dma =S3CMCI_DMA ; +#ifdef CONFIG_MMC_S3C_PIODMA + host->dodma = host->pdata->dma; +#endif + host->mem =platform_get_resource (pdev,IORESOURCE_MEM ,0 );if (!host->mem) { dev_err(&pdev->dev,
@@ -1702,7 +1724,8 @@ static int __devinit s3cmci_probe(structs3cmci_debugfs_attach (host);platform_set_drvdata (pdev, mmc); -dev_info (&pdev->dev,"initialisation done.\n" ); +dev_info (&pdev->dev,"%s - using %s\n" ,mmc_hostname (mmc), +s3cmci_host_usedma (host) ?"dma" :"pio" );return 0 ;
arch/arm/plat-s3c24xx/include/plat/mci.h +1 -0
@@ -4,6 +4,7 @@struct s3c24xx_mci_pdata {unsigned int wprotect_invert :1 ;unsigned int detect_invert :1 ;/* set => detect active high. */ +unsigned int use_dma :1 ;unsigned int gpio_detect;unsigned int gpio_wprotect;
PATCH 9/9 s3cmci: DMA fixes
In reply to Ben DooksFixes for the DMA transfer mode of the driver to try and improve the state of the code:
- Ensure that dma_complete is set during the end of the command phase so that transfers do not stall awaiting the completion
- Update the DMA debugging to provide a bit more useful information such as how many DMA descriptors where not processed and print the DMA addresses in hexadecimal.
- Fix the DMA channel request code to actually request DMA for the S3CMCI block instead of whatever '0' signified.
- Add fallback to PIO if we cannot get the DMA channel, as many of the devices with this block only have a limited number of DMA channels.
- Only try and claim and free the DMA channel if we are trying to use it.
This improves the driver DMA code to the point where it can now identify a card and read the partition table. However the DMA can still stall when trying to move data between the host and memory.
--- drivers/mmc/host/s3cmci.c | 62 ++++++++++++++++++++++++++++++++++------------ drivers/mmc/host/s3cmci.h | 3 -- 2 files changed, 47 insertions(+), 18 deletions(-)
drivers/mmc/host/s3cmci.c +47 -15
@@ -183,6 +183,21 @@ static inline bool s3cmci_host_usedma(st#endif } +/** +* s3cmci_host_canpio - return true if host has pio code available +* +* Return true if the driver has been compiled with the PIO support code +* available. +*/ +static inline bool s3cmci_host_canpio (void ) +{ +#ifdef CONFIG_MMC_S3C_PIO +return true; +#else +return false; +#endif +} +static inline u32 enable_imask (struct s3cmci_host *host,u32 imask) {u32 newmask;
@@ -786,6 +801,7 @@ static void s3cmci_dma_done_callback(strdbg (host, dbg_dma,"DMA FINISHED Size:%i DSTA:%08x DCNT:%08x\n" , size, mci_dsta, mci_dcnt); + host->dma_complete =1 ; host->complete_what =COMPLETION_FINALIZE ; out:
@@ -816,7 +832,8 @@ static void finalize_request(struct s3cmif (cmd->data && (cmd->error ==0 ) && (cmd->data->error ==0 )) {if (s3cmci_host_usedma (host) && (!host->dma_complete)) { -dbg (host, dbg_dma,"DMA Missing!\n" ); +dbg (host, dbg_dma,"DMA Missing (%d)!\n" , + host->dma_complete);return ; } }
@@ -1065,7 +1082,7 @@ static int s3cmci_prepare_pio(struct s3cstatic int s3cmci_prepare_dma (struct s3cmci_host *host,struct mmc_data *data) {int dma_len, i; -int rw = (data->flags &MMC_DATA_WRITE ) ?1 :0 ; +int rw = data->flags &MMC_DATA_WRITE ;BUG_ON ((data->flags &BOTH_DIR ) ==BOTH_DIR );
@@ -1073,7 +1090,7 @@ static int s3cmci_prepare_dma(struct s3cs3c2410_dma_ctrl (host->dma,S3C2410_DMAOP_FLUSH ); dma_len =dma_map_sg (mmc_dev (host->mmc), data->sg, data->sg_len, - (rw) ?DMA_TO_DEVICE :DMA_FROM_DEVICE ); + rw ?DMA_TO_DEVICE :DMA_FROM_DEVICE );if (dma_len ==0 )return -ENOMEM ;
@@ -1084,11 +1101,11 @@ static int s3cmci_prepare_dma(struct s3cfor (i =0 ; i < dma_len; i++) {int res; -dbg (host, dbg_dma,"enqueue %i:%u@%u\n" , i, -sg_dma_address (&data->sg[i]), -sg_dma_len (&data->sg[i])); +dbg (host, dbg_dma,"enqueue %i: %08x@%u\n" , i, +sg_dma_address (&data->sg[i]), +sg_dma_len (&data->sg[i])); - res =s3c2410_dma_enqueue (host->dma, (void *) host, + res =s3c2410_dma_enqueue (host->dma, host,sg_dma_address (&data->sg[i]),sg_dma_len (&data->sg[i]));
@@ -1581,8 +1598,6 @@ static int __devinit s3cmci_probe(struct host->complete_what =COMPLETION_NONE ; host->pio_active =XFER_NONE ; - host->dma =S3CMCI_DMA ; -#ifdef CONFIG_MMC_S3C_PIODMA host->dodma = host->pdata->dma;#endif
@@ -1665,10 +1680,21 @@ static int __devinit s3cmci_probe(structgpio_direction_input (host->pdata->gpio_wprotect); } -if (s3c2410_dma_request (S3CMCI_DMA , &s3cmci_dma_client,NULL ) <0 ) { -dev_err (&pdev->dev,"unable to get DMA channel.\n" ); - ret = -EBUSY ; - goto probe_free_gpio_wp; +/* depending on the dma state, get a dma channel to use. */ + +if (s3cmci_host_usedma (host)) { + host->dma =s3c2410_dma_request (DMACH_SDI , &s3cmci_dma_client, + host); +if (host->dma <0 ) { +dev_err (&pdev->dev,"cannot get DMA channel.\n" ); +if (!s3cmci_host_canpio ()) { + ret = -EBUSY ; + goto probe_free_gpio_wp; + }else { +dev_warn (&pdev->dev,"falling back to PIO.\n" ); + host->dodma =0 ; + } + } } host->clk =clk_get (&pdev->dev,"sdi" );
@@ -1676,7 +1702,7 @@ static int __devinit s3cmci_probe(structdev_err (&pdev->dev,"failed to find clock source.\n" ); ret =PTR_ERR (host->clk); host->clk =NULL ; - goto probe_free_host; + goto probe_free_dma; } ret =clk_enable (host->clk);
@@ -1738,6 +1764,10 @@ static int __devinit s3cmci_probe(struct clk_free:clk_put (host->clk); + probe_free_dma: +if (s3cmci_host_usedma (host)) +s3c2410_dma_free (host->dma, &s3cmci_dma_client); + probe_free_gpio_wp:if (host->pdata->gpio_wprotect)gpio_free (host->pdata->gpio_wprotect);
@@ -1796,7 +1826,9 @@ static int __devexit s3cmci_remove(strucclk_put (host->clk);tasklet_disable (&host->pio_tasklet); -s3c2410_dma_free (S3CMCI_DMA , &s3cmci_dma_client); + +if (s3cmci_host_usedma (host)) +s3c2410_dma_free (host->dma, &s3cmci_dma_client);free_irq (host->irq, host);
drivers/mmc/host/s3cmci.h +0 -3
@@ -8,9 +8,6 @@* published by the Free Software Foundation. */ -/* FIXME: DMA Resource management ?! */ -#define S3CMCI_DMA 0 -enum s3cmci_waitfor {COMPLETION_NONE ,COMPLETION_FINALIZE ,