Message ID | 1348478315-10789-1-git-send-email-hechtb@gmail.com |
---|---|
State | New, archived |
Headers | show |
Hi Bastian On Mon, 24 Sep 2012, Bastian Hecht wrote: > The code probes if DMA channels can get allocated and tears them down at > removal/failure if needed. I'm not sure it makes sense to break this code into two patches - the first one makes no sense without the second one. > > Based on Guennadi Liakhovetski's code from the sh_mmcif driver. > > Signed-off-by: Bastian Hecht <hechtb@gmail.com> > --- > v2: incorporated Vikram Narayanan's thoughts > - added a missing "static". > - reordered things to get rid of a forward declaration > > Based on l2-mtd with reverted commit e26c113b4130aefa1d8446602bb5b05cfd646bfe. > > drivers/mtd/nand/sh_flctl.c | 74 ++++++++++++++++++++++++++++++++++++++++++ > include/linux/mtd/sh_flctl.h | 10 ++++++ > 2 files changed, 84 insertions(+), 0 deletions(-) > > diff --git a/drivers/mtd/nand/sh_flctl.c b/drivers/mtd/nand/sh_flctl.c > index 4fbfe96..9659483 100644 > --- a/drivers/mtd/nand/sh_flctl.c > +++ b/drivers/mtd/nand/sh_flctl.c > @@ -106,6 +106,76 @@ static void wait_completion(struct sh_flctl *flctl) > writeb(0x0, FLTRCR(flctl)); > } > > +static void flctl_release_dma(struct sh_flctl *flctl) > +{ > + if (flctl->chan_fifo0_rx) { > + dma_release_channel(flctl->chan_fifo0_rx); You add dmaengine API calls to the file, so, you have to include header(s). dma_release_channel() requires dmaengine.h. > + flctl->chan_fifo0_rx = NULL; > + } > + if (flctl->chan_fifo0_tx) { > + dma_release_channel(flctl->chan_fifo0_tx); > + flctl->chan_fifo0_tx = NULL; > + } > +} > + > +static void flctl_setup_dma(struct sh_flctl *flctl) > +{ > + dma_cap_mask_t mask; > + struct dma_slave_config cfg; > + struct platform_device *pdev = flctl->pdev; > + struct sh_flctl_platform_data *pdata = pdev->dev.platform_data; > + int ret; > + > + if (!pdata) > + return; > + > + if (pdata->slave_id_fifo0_tx <= 0 || pdata->slave_id_fifo0_rx <= 0) > + return; > + > + /* We can only either use DMA for both Tx and Rx or not use it at all */ > + dma_cap_zero(mask); > + dma_cap_set(DMA_SLAVE, mask); > + > + flctl->chan_fifo0_tx = dma_request_channel(mask, shdma_chan_filter, shdma_chan_filter() requires sh_dma.h. And no, including these headers in your header is not quite correct. > + (void *)pdata->slave_id_fifo0_tx); > + dev_dbg(&pdev->dev, "%s: TX: got channel %p\n", __func__, > + flctl->chan_fifo0_tx); > + > + if (!flctl->chan_fifo0_tx) > + return; > + > + cfg.slave_id = pdata->slave_id_fifo0_tx; > + cfg.direction = DMA_MEM_TO_DEV; > + cfg.dst_addr = (dma_addr_t)FLDTFIFO(flctl); > + cfg.src_addr = 0; > + ret = dmaengine_slave_config(flctl->chan_fifo0_tx, &cfg); Ok, you copied my mistake here:-) "cfg" is allocated on stack and never initialised. You set those its members, that are needed by the sh-dma driver, but it can change at any time to begin using further fields from struct dma_slave_config, so, it has to be zeroed first. E.g., you could do + struct dma_slave_config cfg = {.device_fc = false,}; > + if (ret < 0) > + goto err; > + > + flctl->chan_fifo0_rx = dma_request_channel(mask, shdma_chan_filter, > + (void *)pdata->slave_id_fifo0_rx); > + dev_dbg(&pdev->dev, "%s: RX: got channel %p\n", __func__, > + flctl->chan_fifo0_rx); > + > + if (!flctl->chan_fifo0_rx) > + goto err; > + > + cfg.slave_id = pdata->slave_id_fifo0_rx; > + cfg.direction = DMA_DEV_TO_MEM; > + cfg.dst_addr = 0; > + cfg.src_addr = (dma_addr_t)FLDTFIFO(flctl); > + ret = dmaengine_slave_config(flctl->chan_fifo0_rx, &cfg); > + if (ret < 0) > + goto err; > + > + init_completion(&flctl->dma_complete); > + > + return; > + > +err: > + flctl_release_dma(flctl); > +} > + > static void set_addr(struct mtd_info *mtd, int column, int page_addr) > { > struct sh_flctl *flctl = mtd_to_flctl(mtd); > @@ -930,6 +1000,8 @@ static int __devinit flctl_probe(struct platform_device *pdev) > pm_runtime_enable(&pdev->dev); > pm_runtime_resume(&pdev->dev); > > + flctl_setup_dma(flctl); > + > ret = nand_scan_ident(flctl_mtd, 1, NULL); > if (ret) > goto err_chip; > @@ -947,6 +1019,7 @@ static int __devinit flctl_probe(struct platform_device *pdev) > return 0; > > err_chip: > + flctl_release_dma(flctl); > pm_runtime_disable(&pdev->dev); > free_irq(irq, flctl); > err_flste: > @@ -960,6 +1033,7 @@ static int __devexit flctl_remove(struct platform_device *pdev) > { > struct sh_flctl *flctl = platform_get_drvdata(pdev); > > + flctl_release_dma(flctl); > nand_release(&flctl->mtd); > pm_runtime_disable(&pdev->dev); > free_irq(platform_get_irq(pdev, 0), flctl); > diff --git a/include/linux/mtd/sh_flctl.h b/include/linux/mtd/sh_flctl.h > index 01e4b15..20d3f48 100644 > --- a/include/linux/mtd/sh_flctl.h > +++ b/include/linux/mtd/sh_flctl.h > @@ -20,10 +20,12 @@ > #ifndef __SH_FLCTL_H__ > #define __SH_FLCTL_H__ > > +#include <linux/dmaengine.h> > #include <linux/mtd/mtd.h> > #include <linux/mtd/nand.h> > #include <linux/mtd/partitions.h> > #include <linux/pm_qos.h> > +#include <linux/sh_dma.h> Here headers aren't actually needed - you can just forward-declare "struct dma_chan." OTOH, what _is_ indeed needed is <linux/completion.h>. > > /* FLCTL registers */ > #define FLCMNCR(f) (f->reg + 0x0) > @@ -161,6 +163,11 @@ struct sh_flctl { > unsigned hwecc:1; /* Hardware ECC (0 = disabled, 1 = enabled) */ > unsigned holden:1; /* Hardware has FLHOLDCR and HOLDEN is set */ > unsigned qos_request:1; /* QoS request to prevent deep power shutdown */ > + > + /* DMA related objects */ > + struct dma_chan *chan_fifo0_rx; > + struct dma_chan *chan_fifo0_tx; > + struct completion dma_complete; > }; > > struct sh_flctl_platform_data { > @@ -170,6 +177,9 @@ struct sh_flctl_platform_data { > > unsigned has_hwecc:1; > unsigned use_holden:1; > + > + unsigned int slave_id_fifo0_tx; > + unsigned int slave_id_fifo0_rx; > }; > > static inline struct sh_flctl *mtd_to_flctl(struct mtd_info *mtdinfo) > -- > 1.7.5.4 > Thanks Guennadi --- Guennadi Liakhovetski, Ph.D. Freelance Open-Source Software Developer http://www.open-technology.de/
Thanks for the reviews, Guennadi! Thanks too to Vikram - I've forgot to add you as recipient for the v2, so I pull you in here now. 2012/9/27 Guennadi Liakhovetski <g.liakhovetski@gmx.de>: > Hi Bastian > > On Mon, 24 Sep 2012, Bastian Hecht wrote: > >> The code probes if DMA channels can get allocated and tears them down at >> removal/failure if needed. > > I'm not sure it makes sense to break this code into two patches - the > first one makes no sense without the second one. Ok, will merge them. >> >> Based on Guennadi Liakhovetski's code from the sh_mmcif driver. >> >> Signed-off-by: Bastian Hecht <hechtb@gmail.com> >> --- >> v2: incorporated Vikram Narayanan's thoughts >> - added a missing "static". >> - reordered things to get rid of a forward declaration >> >> Based on l2-mtd with reverted commit e26c113b4130aefa1d8446602bb5b05cfd646bfe. >> >> drivers/mtd/nand/sh_flctl.c | 74 ++++++++++++++++++++++++++++++++++++++++++ >> include/linux/mtd/sh_flctl.h | 10 ++++++ >> 2 files changed, 84 insertions(+), 0 deletions(-) >> >> diff --git a/drivers/mtd/nand/sh_flctl.c b/drivers/mtd/nand/sh_flctl.c >> index 4fbfe96..9659483 100644 >> --- a/drivers/mtd/nand/sh_flctl.c >> +++ b/drivers/mtd/nand/sh_flctl.c >> @@ -106,6 +106,76 @@ static void wait_completion(struct sh_flctl *flctl) >> writeb(0x0, FLTRCR(flctl)); >> } >> >> +static void flctl_release_dma(struct sh_flctl *flctl) >> +{ >> + if (flctl->chan_fifo0_rx) { >> + dma_release_channel(flctl->chan_fifo0_rx); > > You add dmaengine API calls to the file, so, you have to include > header(s). dma_release_channel() requires dmaengine.h. Ok. >> + flctl->chan_fifo0_rx = NULL; >> + } >> + if (flctl->chan_fifo0_tx) { >> + dma_release_channel(flctl->chan_fifo0_tx); >> + flctl->chan_fifo0_tx = NULL; >> + } >> +} >> + >> +static void flctl_setup_dma(struct sh_flctl *flctl) >> +{ >> + dma_cap_mask_t mask; >> + struct dma_slave_config cfg; >> + struct platform_device *pdev = flctl->pdev; >> + struct sh_flctl_platform_data *pdata = pdev->dev.platform_data; >> + int ret; >> + >> + if (!pdata) >> + return; >> + >> + if (pdata->slave_id_fifo0_tx <= 0 || pdata->slave_id_fifo0_rx <= 0) >> + return; >> + >> + /* We can only either use DMA for both Tx and Rx or not use it at all */ >> + dma_cap_zero(mask); >> + dma_cap_set(DMA_SLAVE, mask); >> + >> + flctl->chan_fifo0_tx = dma_request_channel(mask, shdma_chan_filter, > > shdma_chan_filter() requires sh_dma.h. And no, including these headers in > your header is not quite correct. Ok. >> + (void *)pdata->slave_id_fifo0_tx); >> + dev_dbg(&pdev->dev, "%s: TX: got channel %p\n", __func__, >> + flctl->chan_fifo0_tx); >> + >> + if (!flctl->chan_fifo0_tx) >> + return; >> + >> + cfg.slave_id = pdata->slave_id_fifo0_tx; >> + cfg.direction = DMA_MEM_TO_DEV; >> + cfg.dst_addr = (dma_addr_t)FLDTFIFO(flctl); >> + cfg.src_addr = 0; >> + ret = dmaengine_slave_config(flctl->chan_fifo0_tx, &cfg); > > Ok, you copied my mistake here:-) "cfg" is allocated on stack and never > initialised. You set those its members, that are needed by the sh-dma > driver, but it can change at any time to begin using further fields from > struct dma_slave_config, so, it has to be zeroed first. E.g., you could do > > + struct dma_slave_config cfg = {.device_fc = false,}; I think I'll use memset. Taking a member like .device_fc may be a bit misleading. We are not interested in it and leave it on the default value. And we have no other field we could initialize that won't be changed in the function. >> + if (ret < 0) >> + goto err; >> + >> + flctl->chan_fifo0_rx = dma_request_channel(mask, shdma_chan_filter, >> + (void *)pdata->slave_id_fifo0_rx); >> + dev_dbg(&pdev->dev, "%s: RX: got channel %p\n", __func__, >> + flctl->chan_fifo0_rx); >> + >> + if (!flctl->chan_fifo0_rx) >> + goto err; >> + >> + cfg.slave_id = pdata->slave_id_fifo0_rx; >> + cfg.direction = DMA_DEV_TO_MEM; >> + cfg.dst_addr = 0; >> + cfg.src_addr = (dma_addr_t)FLDTFIFO(flctl); >> + ret = dmaengine_slave_config(flctl->chan_fifo0_rx, &cfg); >> + if (ret < 0) >> + goto err; >> + >> + init_completion(&flctl->dma_complete); >> + >> + return; >> + >> +err: >> + flctl_release_dma(flctl); >> +} >> + >> static void set_addr(struct mtd_info *mtd, int column, int page_addr) >> { >> struct sh_flctl *flctl = mtd_to_flctl(mtd); >> @@ -930,6 +1000,8 @@ static int __devinit flctl_probe(struct platform_device *pdev) >> pm_runtime_enable(&pdev->dev); >> pm_runtime_resume(&pdev->dev); >> >> + flctl_setup_dma(flctl); >> + >> ret = nand_scan_ident(flctl_mtd, 1, NULL); >> if (ret) >> goto err_chip; >> @@ -947,6 +1019,7 @@ static int __devinit flctl_probe(struct platform_device *pdev) >> return 0; >> >> err_chip: >> + flctl_release_dma(flctl); >> pm_runtime_disable(&pdev->dev); >> free_irq(irq, flctl); >> err_flste: >> @@ -960,6 +1033,7 @@ static int __devexit flctl_remove(struct platform_device *pdev) >> { >> struct sh_flctl *flctl = platform_get_drvdata(pdev); >> >> + flctl_release_dma(flctl); >> nand_release(&flctl->mtd); >> pm_runtime_disable(&pdev->dev); >> free_irq(platform_get_irq(pdev, 0), flctl); >> diff --git a/include/linux/mtd/sh_flctl.h b/include/linux/mtd/sh_flctl.h >> index 01e4b15..20d3f48 100644 >> --- a/include/linux/mtd/sh_flctl.h >> +++ b/include/linux/mtd/sh_flctl.h >> @@ -20,10 +20,12 @@ >> #ifndef __SH_FLCTL_H__ >> #define __SH_FLCTL_H__ >> >> +#include <linux/dmaengine.h> >> #include <linux/mtd/mtd.h> >> #include <linux/mtd/nand.h> >> #include <linux/mtd/partitions.h> >> #include <linux/pm_qos.h> >> +#include <linux/sh_dma.h> > > Here headers aren't actually needed - you can just forward-declare "struct > dma_chan." OTOH, what _is_ indeed needed is <linux/completion.h>. Oh yes, I'll add <linux/completion.h>. About the forward declration: Do I really need it? We use a struct pointer and I am unsure if the compiler just needs to know the name and nothing more. I've tested it and included sh_flctl.h (without any DMA headers) in some driver that has no idea about DMA. gcc didn't complain. Adding a non-pointer member + struct sh_dma test; lead to an error, so struct sh_dma is really unknown. >> >> /* FLCTL registers */ >> #define FLCMNCR(f) (f->reg + 0x0) >> @@ -161,6 +163,11 @@ struct sh_flctl { >> unsigned hwecc:1; /* Hardware ECC (0 = disabled, 1 = enabled) */ >> unsigned holden:1; /* Hardware has FLHOLDCR and HOLDEN is set */ >> unsigned qos_request:1; /* QoS request to prevent deep power shutdown */ >> + >> + /* DMA related objects */ >> + struct dma_chan *chan_fifo0_rx; >> + struct dma_chan *chan_fifo0_tx; >> + struct completion dma_complete; >> }; >> >> struct sh_flctl_platform_data { >> @@ -170,6 +177,9 @@ struct sh_flctl_platform_data { >> >> unsigned has_hwecc:1; >> unsigned use_holden:1; >> + >> + unsigned int slave_id_fifo0_tx; >> + unsigned int slave_id_fifo0_rx; >> }; >> >> static inline struct sh_flctl *mtd_to_flctl(struct mtd_info *mtdinfo) >> -- >> 1.7.5.4 >> > > Thanks > Guennadi > --- > Guennadi Liakhovetski, Ph.D. > Freelance Open-Source Software Developer > http://www.open-technology.de/ > -- > To unsubscribe from this list: send the line "unsubscribe linux-sh" in > the body of a message to majordomo@vger.kernel.org > More majordomo info at http://vger.kernel.org/majordomo-info.html
On Fri, 28 Sep 2012, Bastian Hecht wrote: [snip] > >> diff --git a/include/linux/mtd/sh_flctl.h b/include/linux/mtd/sh_flctl.h > >> index 01e4b15..20d3f48 100644 > >> --- a/include/linux/mtd/sh_flctl.h > >> +++ b/include/linux/mtd/sh_flctl.h > >> @@ -20,10 +20,12 @@ > >> #ifndef __SH_FLCTL_H__ > >> #define __SH_FLCTL_H__ > >> > >> +#include <linux/dmaengine.h> > >> #include <linux/mtd/mtd.h> > >> #include <linux/mtd/nand.h> > >> #include <linux/mtd/partitions.h> > >> #include <linux/pm_qos.h> > >> +#include <linux/sh_dma.h> > > > > Here headers aren't actually needed - you can just forward-declare "struct > > dma_chan." OTOH, what _is_ indeed needed is <linux/completion.h>. > > Oh yes, I'll add <linux/completion.h>. > About the forward declration: Do I really need it? We use a struct > pointer and I am unsure if the compiler just needs to know the name > and nothing more. I've tested it and included sh_flctl.h (without any > DMA headers) in some driver that has no idea about DMA. > gcc didn't complain. I'm not sure about this, I think, sometimes there might be cases, when gcc doesn't complain. Or (more likely) the header is pulled in via another one, or some other header has this forward-declaration. But in any case I wouldn't rely on either of those and declare what's needed explicitly. > Adding a non-pointer member > + struct sh_dma test; > lead to an error, so struct sh_dma is really unknown. Of course, the compiler has to know the size of the struct to allocate space for it:-) Thanks Guennadi --- Guennadi Liakhovetski, Ph.D. Freelance Open-Source Software Developer http://www.open-technology.de/
2012/9/28 Guennadi Liakhovetski <g.liakhovetski@gmx.de>: > On Fri, 28 Sep 2012, Bastian Hecht wrote: > > [snip] > >> Oh yes, I'll add <linux/completion.h>. >> About the forward declration: Do I really need it? We use a struct >> pointer and I am unsure if the compiler just needs to know the name >> and nothing more. I've tested it and included sh_flctl.h (without any >> DMA headers) in some driver that has no idea about DMA. >> gcc didn't complain. > > I'm not sure about this, I think, sometimes there might be cases, when gcc > doesn't complain. Or (more likely) the header is pulled in via another > one, or some other header has this forward-declaration. But in any case I > wouldn't rely on either of those and declare what's needed explicitly. > >> Adding a non-pointer member >> + struct sh_dma test; >> lead to an error, so struct sh_dma is really unknown. > > Of course, the compiler has to know the size of the struct to allocate > space for it:-) > Ah sure - I thought I catch any header pull-ins by this test but yeah - it can be just forward declarations too... So I will post a merged v3 under the new title: "mtd: sh_flctl: Add DMA capabilty". It includes the critics from here plus 2 other things: - "Fall back to PIO" now actually falls back for the current read operation and not just for future reads - I removed the init_completion() after each DMA read. The completion gets set up at probe time and what I read on the internet and in researched in the sources we don't need to reset it then after each usage. thanks, Bastian
diff --git a/drivers/mtd/nand/sh_flctl.c b/drivers/mtd/nand/sh_flctl.c index 4fbfe96..9659483 100644 --- a/drivers/mtd/nand/sh_flctl.c +++ b/drivers/mtd/nand/sh_flctl.c @@ -106,6 +106,76 @@ static void wait_completion(struct sh_flctl *flctl) writeb(0x0, FLTRCR(flctl)); } +static void flctl_release_dma(struct sh_flctl *flctl) +{ + if (flctl->chan_fifo0_rx) { + dma_release_channel(flctl->chan_fifo0_rx); + flctl->chan_fifo0_rx = NULL; + } + if (flctl->chan_fifo0_tx) { + dma_release_channel(flctl->chan_fifo0_tx); + flctl->chan_fifo0_tx = NULL; + } +} + +static void flctl_setup_dma(struct sh_flctl *flctl) +{ + dma_cap_mask_t mask; + struct dma_slave_config cfg; + struct platform_device *pdev = flctl->pdev; + struct sh_flctl_platform_data *pdata = pdev->dev.platform_data; + int ret; + + if (!pdata) + return; + + if (pdata->slave_id_fifo0_tx <= 0 || pdata->slave_id_fifo0_rx <= 0) + return; + + /* We can only either use DMA for both Tx and Rx or not use it at all */ + dma_cap_zero(mask); + dma_cap_set(DMA_SLAVE, mask); + + flctl->chan_fifo0_tx = dma_request_channel(mask, shdma_chan_filter, + (void *)pdata->slave_id_fifo0_tx); + dev_dbg(&pdev->dev, "%s: TX: got channel %p\n", __func__, + flctl->chan_fifo0_tx); + + if (!flctl->chan_fifo0_tx) + return; + + cfg.slave_id = pdata->slave_id_fifo0_tx; + cfg.direction = DMA_MEM_TO_DEV; + cfg.dst_addr = (dma_addr_t)FLDTFIFO(flctl); + cfg.src_addr = 0; + ret = dmaengine_slave_config(flctl->chan_fifo0_tx, &cfg); + if (ret < 0) + goto err; + + flctl->chan_fifo0_rx = dma_request_channel(mask, shdma_chan_filter, + (void *)pdata->slave_id_fifo0_rx); + dev_dbg(&pdev->dev, "%s: RX: got channel %p\n", __func__, + flctl->chan_fifo0_rx); + + if (!flctl->chan_fifo0_rx) + goto err; + + cfg.slave_id = pdata->slave_id_fifo0_rx; + cfg.direction = DMA_DEV_TO_MEM; + cfg.dst_addr = 0; + cfg.src_addr = (dma_addr_t)FLDTFIFO(flctl); + ret = dmaengine_slave_config(flctl->chan_fifo0_rx, &cfg); + if (ret < 0) + goto err; + + init_completion(&flctl->dma_complete); + + return; + +err: + flctl_release_dma(flctl); +} + static void set_addr(struct mtd_info *mtd, int column, int page_addr) { struct sh_flctl *flctl = mtd_to_flctl(mtd); @@ -930,6 +1000,8 @@ static int __devinit flctl_probe(struct platform_device *pdev) pm_runtime_enable(&pdev->dev); pm_runtime_resume(&pdev->dev); + flctl_setup_dma(flctl); + ret = nand_scan_ident(flctl_mtd, 1, NULL); if (ret) goto err_chip; @@ -947,6 +1019,7 @@ static int __devinit flctl_probe(struct platform_device *pdev) return 0; err_chip: + flctl_release_dma(flctl); pm_runtime_disable(&pdev->dev); free_irq(irq, flctl); err_flste: @@ -960,6 +1033,7 @@ static int __devexit flctl_remove(struct platform_device *pdev) { struct sh_flctl *flctl = platform_get_drvdata(pdev); + flctl_release_dma(flctl); nand_release(&flctl->mtd); pm_runtime_disable(&pdev->dev); free_irq(platform_get_irq(pdev, 0), flctl); diff --git a/include/linux/mtd/sh_flctl.h b/include/linux/mtd/sh_flctl.h index 01e4b15..20d3f48 100644 --- a/include/linux/mtd/sh_flctl.h +++ b/include/linux/mtd/sh_flctl.h @@ -20,10 +20,12 @@ #ifndef __SH_FLCTL_H__ #define __SH_FLCTL_H__ +#include <linux/dmaengine.h> #include <linux/mtd/mtd.h> #include <linux/mtd/nand.h> #include <linux/mtd/partitions.h> #include <linux/pm_qos.h> +#include <linux/sh_dma.h> /* FLCTL registers */ #define FLCMNCR(f) (f->reg + 0x0) @@ -161,6 +163,11 @@ struct sh_flctl { unsigned hwecc:1; /* Hardware ECC (0 = disabled, 1 = enabled) */ unsigned holden:1; /* Hardware has FLHOLDCR and HOLDEN is set */ unsigned qos_request:1; /* QoS request to prevent deep power shutdown */ + + /* DMA related objects */ + struct dma_chan *chan_fifo0_rx; + struct dma_chan *chan_fifo0_tx; + struct completion dma_complete; }; struct sh_flctl_platform_data { @@ -170,6 +177,9 @@ struct sh_flctl_platform_data { unsigned has_hwecc:1; unsigned use_holden:1; + + unsigned int slave_id_fifo0_tx; + unsigned int slave_id_fifo0_rx; }; static inline struct sh_flctl *mtd_to_flctl(struct mtd_info *mtdinfo)
The code probes if DMA channels can get allocated and tears them down at removal/failure if needed. Based on Guennadi Liakhovetski's code from the sh_mmcif driver. Signed-off-by: Bastian Hecht <hechtb@gmail.com> --- v2: incorporated Vikram Narayanan's thoughts - added a missing "static". - reordered things to get rid of a forward declaration Based on l2-mtd with reverted commit e26c113b4130aefa1d8446602bb5b05cfd646bfe. drivers/mtd/nand/sh_flctl.c | 74 ++++++++++++++++++++++++++++++++++++++++++ include/linux/mtd/sh_flctl.h | 10 ++++++ 2 files changed, 84 insertions(+), 0 deletions(-)