bcm47xx: Add support for brcmnand controller on BCMA bus
Back port the patches being submitted upstream in order to make the NAND controller work on BCM47187/5358. This is a prerequisite for supporting devices like the Netgear WNR3500L V2. Signed-off-by: Florian Fainelli <f.fainelli@gmail.com>
This commit is contained in:
		| @@ -147,8 +147,11 @@ CONFIG_MIPS_L1_CACHE_SHIFT=5 | ||||
| CONFIG_MODULES_USE_ELF_REL=y | ||||
| CONFIG_MTD_BCM47XXSFLASH=y | ||||
| CONFIG_MTD_BCM47XX_PARTS=y | ||||
| CONFIG_MTD_NAND=y | ||||
| CONFIG_MTD_NAND_CORE=y | ||||
| CONFIG_MTD_RAW_NAND=y | ||||
| CONFIG_MTD_NAND_BCM47XXNFLASH=y | ||||
| CONFIG_MTD_NAND_BRCMNAND=y | ||||
| CONFIG_MTD_NAND_BRCMNAND_BCMA=y | ||||
| CONFIG_MTD_NAND_ECC=y | ||||
| CONFIG_MTD_PARSER_TRX=y | ||||
| CONFIG_MTD_PHYSMAP=y | ||||
|   | ||||
| @@ -0,0 +1,33 @@ | ||||
| From: Florian Fainelli <f.fainelli@gmail.com> | ||||
| Subject: [PATCH v3 1/9] mtd: rawnand: brcmnand: Assign soc as early as possible | ||||
| Date: Fri, 07 Jan 2022 10:46:06 -0800 | ||||
| Content-Type: text/plain; charset="utf-8" | ||||
|  | ||||
| In order to key off the brcmnand_probe() code in subsequent changes | ||||
| depending upon ctrl->soc, assign that variable as early as possible, | ||||
| instead of much later when we have checked that it is non-NULL. | ||||
|  | ||||
| Signed-off-by: Florian Fainelli <f.fainelli@gmail.com> | ||||
| --- | ||||
|  drivers/mtd/nand/raw/brcmnand/brcmnand.c | 3 +-- | ||||
|  1 file changed, 1 insertion(+), 2 deletions(-) | ||||
|  | ||||
| --- a/drivers/mtd/nand/raw/brcmnand/brcmnand.c | ||||
| +++ b/drivers/mtd/nand/raw/brcmnand/brcmnand.c | ||||
| @@ -2949,6 +2949,7 @@ int brcmnand_probe(struct platform_devic | ||||
|   | ||||
|  	dev_set_drvdata(dev, ctrl); | ||||
|  	ctrl->dev = dev; | ||||
| +	ctrl->soc = soc; | ||||
|   | ||||
|  	init_completion(&ctrl->done); | ||||
|  	init_completion(&ctrl->dma_done); | ||||
| @@ -3089,8 +3090,6 @@ int brcmnand_probe(struct platform_devic | ||||
|  	 * interesting ways | ||||
|  	 */ | ||||
|  	if (soc) { | ||||
| -		ctrl->soc = soc; | ||||
| - | ||||
|  		ret = devm_request_irq(dev, ctrl->irq, brcmnand_irq, 0, | ||||
|  				       DRV_NAME, ctrl); | ||||
|   | ||||
| @@ -0,0 +1,150 @@ | ||||
| From: Florian Fainelli <f.fainelli@gmail.com> | ||||
| Subject: [PATCH v3 2/9] mtd: rawnand: brcmnand: Allow SoC to provide I/O operations | ||||
| Date: Fri, 07 Jan 2022 10:46:07 -0800 | ||||
| Content-Type: text/plain; charset="utf-8" | ||||
|  | ||||
| Allow a brcmnand_soc instance to provide a custom set of I/O operations | ||||
| which we will require when using this driver on a BCMA bus which is not | ||||
| directly memory mapped I/O. Update the nand_{read,write}_reg accordingly | ||||
| to use the SoC operations if provided. | ||||
|  | ||||
| To minimize the penalty on other SoCs which do support standard MMIO | ||||
| accesses, we use a static key which is disabled by default and gets | ||||
| enabled if a soc implementation does provide I/O operations. | ||||
|  | ||||
| Signed-off-by: Florian Fainelli <f.fainelli@gmail.com> | ||||
| --- | ||||
|  drivers/mtd/nand/raw/brcmnand/brcmnand.c | 28 +++++++++++++++++++++-- | ||||
|  drivers/mtd/nand/raw/brcmnand/brcmnand.h | 29 ++++++++++++++++++++++++ | ||||
|  2 files changed, 55 insertions(+), 2 deletions(-) | ||||
|  | ||||
| --- a/drivers/mtd/nand/raw/brcmnand/brcmnand.c | ||||
| +++ b/drivers/mtd/nand/raw/brcmnand/brcmnand.c | ||||
| @@ -25,6 +25,7 @@ | ||||
|  #include <linux/of.h> | ||||
|  #include <linux/of_platform.h> | ||||
|  #include <linux/slab.h> | ||||
| +#include <linux/static_key.h> | ||||
|  #include <linux/list.h> | ||||
|  #include <linux/log2.h> | ||||
|   | ||||
| @@ -207,6 +208,8 @@ enum { | ||||
|   | ||||
|  struct brcmnand_host; | ||||
|   | ||||
| +static DEFINE_STATIC_KEY_FALSE(brcmnand_soc_has_ops_key); | ||||
| + | ||||
|  struct brcmnand_controller { | ||||
|  	struct device		*dev; | ||||
|  	struct nand_controller	controller; | ||||
| @@ -589,15 +592,25 @@ enum { | ||||
|  	INTFC_CTLR_READY		= BIT(31), | ||||
|  }; | ||||
|   | ||||
| +static inline bool brcmnand_non_mmio_ops(struct brcmnand_controller *ctrl) | ||||
| +{ | ||||
| +	return static_branch_unlikely(&brcmnand_soc_has_ops_key); | ||||
| +} | ||||
| + | ||||
|  static inline u32 nand_readreg(struct brcmnand_controller *ctrl, u32 offs) | ||||
|  { | ||||
| +	if (brcmnand_non_mmio_ops(ctrl)) | ||||
| +		return brcmnand_soc_read(ctrl->soc, offs); | ||||
|  	return brcmnand_readl(ctrl->nand_base + offs); | ||||
|  } | ||||
|   | ||||
|  static inline void nand_writereg(struct brcmnand_controller *ctrl, u32 offs, | ||||
|  				 u32 val) | ||||
|  { | ||||
| -	brcmnand_writel(val, ctrl->nand_base + offs); | ||||
| +	if (brcmnand_non_mmio_ops(ctrl)) | ||||
| +		brcmnand_soc_write(ctrl->soc, val, offs); | ||||
| +	else | ||||
| +		brcmnand_writel(val, ctrl->nand_base + offs); | ||||
|  } | ||||
|   | ||||
|  static int brcmnand_revision_init(struct brcmnand_controller *ctrl) | ||||
| @@ -763,13 +776,18 @@ static inline void brcmnand_rmw_reg(stru | ||||
|   | ||||
|  static inline u32 brcmnand_read_fc(struct brcmnand_controller *ctrl, int word) | ||||
|  { | ||||
| +	if (brcmnand_non_mmio_ops(ctrl)) | ||||
| +		return brcmnand_soc_read(ctrl->soc, BRCMNAND_NON_MMIO_FC_ADDR); | ||||
|  	return __raw_readl(ctrl->nand_fc + word * 4); | ||||
|  } | ||||
|   | ||||
|  static inline void brcmnand_write_fc(struct brcmnand_controller *ctrl, | ||||
|  				     int word, u32 val) | ||||
|  { | ||||
| -	__raw_writel(val, ctrl->nand_fc + word * 4); | ||||
| +	if (brcmnand_non_mmio_ops(ctrl)) | ||||
| +		brcmnand_soc_write(ctrl->soc, val, BRCMNAND_NON_MMIO_FC_ADDR); | ||||
| +	else | ||||
| +		__raw_writel(val, ctrl->nand_fc + word * 4); | ||||
|  } | ||||
|   | ||||
|  static inline void edu_writel(struct brcmnand_controller *ctrl, | ||||
| @@ -2951,6 +2969,12 @@ int brcmnand_probe(struct platform_devic | ||||
|  	ctrl->dev = dev; | ||||
|  	ctrl->soc = soc; | ||||
|   | ||||
| +	/* Enable the static key if the soc provides I/O operations indicating | ||||
| +	 * that a non-memory mapped IO access path must be used | ||||
| +	 */ | ||||
| +	if (brcmnand_soc_has_ops(ctrl->soc)) | ||||
| +		static_branch_enable(&brcmnand_soc_has_ops_key); | ||||
| + | ||||
|  	init_completion(&ctrl->done); | ||||
|  	init_completion(&ctrl->dma_done); | ||||
|  	init_completion(&ctrl->edu_done); | ||||
| --- a/drivers/mtd/nand/raw/brcmnand/brcmnand.h | ||||
| +++ b/drivers/mtd/nand/raw/brcmnand/brcmnand.h | ||||
| @@ -11,12 +11,25 @@ | ||||
|   | ||||
|  struct platform_device; | ||||
|  struct dev_pm_ops; | ||||
| +struct brcmnand_io_ops; | ||||
| + | ||||
| +/* Special register offset constant to intercept a non-MMIO access | ||||
| + * to the flash cache register space. This is intentionally large | ||||
| + * not to overlap with an existing offset. | ||||
| + */ | ||||
| +#define BRCMNAND_NON_MMIO_FC_ADDR	0xffffffff | ||||
|   | ||||
|  struct brcmnand_soc { | ||||
|  	bool (*ctlrdy_ack)(struct brcmnand_soc *soc); | ||||
|  	void (*ctlrdy_set_enabled)(struct brcmnand_soc *soc, bool en); | ||||
|  	void (*prepare_data_bus)(struct brcmnand_soc *soc, bool prepare, | ||||
|  				 bool is_param); | ||||
| +	const struct brcmnand_io_ops *ops; | ||||
| +}; | ||||
| + | ||||
| +struct brcmnand_io_ops { | ||||
| +	u32 (*read_reg)(struct brcmnand_soc *soc, u32 offset); | ||||
| +	void (*write_reg)(struct brcmnand_soc *soc, u32 val, u32 offset); | ||||
|  }; | ||||
|   | ||||
|  static inline void brcmnand_soc_data_bus_prepare(struct brcmnand_soc *soc, | ||||
| @@ -58,6 +71,22 @@ static inline void brcmnand_writel(u32 v | ||||
|  		writel_relaxed(val, addr); | ||||
|  } | ||||
|   | ||||
| +static inline bool brcmnand_soc_has_ops(struct brcmnand_soc *soc) | ||||
| +{ | ||||
| +	return soc && soc->ops && soc->ops->read_reg && soc->ops->write_reg; | ||||
| +} | ||||
| + | ||||
| +static inline u32 brcmnand_soc_read(struct brcmnand_soc *soc, u32 offset) | ||||
| +{ | ||||
| +	return soc->ops->read_reg(soc, offset); | ||||
| +} | ||||
| + | ||||
| +static inline void brcmnand_soc_write(struct brcmnand_soc *soc, u32 val, | ||||
| +				      u32 offset) | ||||
| +{ | ||||
| +	soc->ops->write_reg(soc, val, offset); | ||||
| +} | ||||
| + | ||||
|  int brcmnand_probe(struct platform_device *pdev, struct brcmnand_soc *soc); | ||||
|  int brcmnand_remove(struct platform_device *pdev); | ||||
|   | ||||
| @@ -0,0 +1,52 @@ | ||||
| From: Florian Fainelli <f.fainelli@gmail.com> | ||||
| Subject: [PATCH v3 3/9] mtd: rawnand: brcmnand: Avoid pdev in brcmnand_init_cs() | ||||
| Date: Fri, 07 Jan 2022 10:46:08 -0800 | ||||
| Content-Type: text/plain; charset="utf-8" | ||||
|  | ||||
| In preparation for encapsulating more of what the loop calling | ||||
| brcmnand_init_cs() does, avoid using platform_device when it is the | ||||
| device behind platform_device that we are using for printing errors. | ||||
|  | ||||
| No functional changes introduced. | ||||
|  | ||||
| Signed-off-by: Florian Fainelli <f.fainelli@gmail.com> | ||||
| --- | ||||
|  drivers/mtd/nand/raw/brcmnand/brcmnand.c | 8 ++++---- | ||||
|  1 file changed, 4 insertions(+), 4 deletions(-) | ||||
|  | ||||
| --- a/drivers/mtd/nand/raw/brcmnand/brcmnand.c | ||||
| +++ b/drivers/mtd/nand/raw/brcmnand/brcmnand.c | ||||
| @@ -2722,7 +2722,7 @@ static const struct nand_controller_ops | ||||
|  static int brcmnand_init_cs(struct brcmnand_host *host, struct device_node *dn) | ||||
|  { | ||||
|  	struct brcmnand_controller *ctrl = host->ctrl; | ||||
| -	struct platform_device *pdev = host->pdev; | ||||
| +	struct device *dev = ctrl->dev; | ||||
|  	struct mtd_info *mtd; | ||||
|  	struct nand_chip *chip; | ||||
|  	int ret; | ||||
| @@ -2730,7 +2730,7 @@ static int brcmnand_init_cs(struct brcmn | ||||
|   | ||||
|  	ret = of_property_read_u32(dn, "reg", &host->cs); | ||||
|  	if (ret) { | ||||
| -		dev_err(&pdev->dev, "can't get chip-select\n"); | ||||
| +		dev_err(dev, "can't get chip-select\n"); | ||||
|  		return -ENXIO; | ||||
|  	} | ||||
|   | ||||
| @@ -2739,13 +2739,13 @@ static int brcmnand_init_cs(struct brcmn | ||||
|   | ||||
|  	nand_set_flash_node(chip, dn); | ||||
|  	nand_set_controller_data(chip, host); | ||||
| -	mtd->name = devm_kasprintf(&pdev->dev, GFP_KERNEL, "brcmnand.%d", | ||||
| +	mtd->name = devm_kasprintf(dev, GFP_KERNEL, "brcmnand.%d", | ||||
|  				   host->cs); | ||||
|  	if (!mtd->name) | ||||
|  		return -ENOMEM; | ||||
|   | ||||
|  	mtd->owner = THIS_MODULE; | ||||
| -	mtd->dev.parent = &pdev->dev; | ||||
| +	mtd->dev.parent = dev; | ||||
|   | ||||
|  	chip->legacy.cmd_ctrl = brcmnand_cmd_ctrl; | ||||
|  	chip->legacy.cmdfunc = brcmnand_cmdfunc; | ||||
| @@ -0,0 +1,63 @@ | ||||
| From: Florian Fainelli <f.fainelli@gmail.com> | ||||
| Subject: [PATCH v3 4/9] mtd: rawnand: brcmnand: Move OF operations out of brcmnand_init_cs() | ||||
| Date: Fri, 07 Jan 2022 10:46:09 -0800 | ||||
| Content-Type: text/plain; charset="utf-8" | ||||
|  | ||||
| In order to initialize a given chip select object for use by the | ||||
| brcmnand driver, move all of the Device Tree specific routines outside | ||||
| of brcmnand_init_cs() in order to make it usable in a platform data | ||||
| configuration which will be necessary for supporting BCMA chips. | ||||
|  | ||||
| No functional changes introduced. | ||||
|  | ||||
| Signed-off-by: Florian Fainelli <f.fainelli@gmail.com> | ||||
| --- | ||||
|  drivers/mtd/nand/raw/brcmnand/brcmnand.c | 20 +++++++++++--------- | ||||
|  1 file changed, 11 insertions(+), 9 deletions(-) | ||||
|  | ||||
| --- a/drivers/mtd/nand/raw/brcmnand/brcmnand.c | ||||
| +++ b/drivers/mtd/nand/raw/brcmnand/brcmnand.c | ||||
| @@ -2719,7 +2719,7 @@ static const struct nand_controller_ops | ||||
|  	.attach_chip = brcmnand_attach_chip, | ||||
|  }; | ||||
|   | ||||
| -static int brcmnand_init_cs(struct brcmnand_host *host, struct device_node *dn) | ||||
| +static int brcmnand_init_cs(struct brcmnand_host *host) | ||||
|  { | ||||
|  	struct brcmnand_controller *ctrl = host->ctrl; | ||||
|  	struct device *dev = ctrl->dev; | ||||
| @@ -2728,16 +2728,9 @@ static int brcmnand_init_cs(struct brcmn | ||||
|  	int ret; | ||||
|  	u16 cfg_offs; | ||||
|   | ||||
| -	ret = of_property_read_u32(dn, "reg", &host->cs); | ||||
| -	if (ret) { | ||||
| -		dev_err(dev, "can't get chip-select\n"); | ||||
| -		return -ENXIO; | ||||
| -	} | ||||
| - | ||||
|  	mtd = nand_to_mtd(&host->chip); | ||||
|  	chip = &host->chip; | ||||
|   | ||||
| -	nand_set_flash_node(chip, dn); | ||||
|  	nand_set_controller_data(chip, host); | ||||
|  	mtd->name = devm_kasprintf(dev, GFP_KERNEL, "brcmnand.%d", | ||||
|  				   host->cs); | ||||
| @@ -3144,7 +3137,16 @@ int brcmnand_probe(struct platform_devic | ||||
|  			host->pdev = pdev; | ||||
|  			host->ctrl = ctrl; | ||||
|   | ||||
| -			ret = brcmnand_init_cs(host, child); | ||||
| +			ret = of_property_read_u32(child, "reg", &host->cs); | ||||
| +			if (ret) { | ||||
| +				dev_err(dev, "can't get chip-select\n"); | ||||
| +				devm_kfree(dev, host); | ||||
| +				continue; | ||||
| +			} | ||||
| + | ||||
| +			nand_set_flash_node(&host->chip, child); | ||||
| + | ||||
| +			ret = brcmnand_init_cs(host); | ||||
|  			if (ret) { | ||||
|  				devm_kfree(dev, host); | ||||
|  				continue; /* Try all chip-selects */ | ||||
| @@ -0,0 +1,91 @@ | ||||
| From: Florian Fainelli <f.fainelli@gmail.com> | ||||
| Subject: [PATCH v3 5/9] mtd: rawnand: brcmnand: Allow working without interrupts | ||||
| Date: Fri, 07 Jan 2022 10:46:10 -0800 | ||||
| Content-Type: text/plain; charset="utf-8" | ||||
|  | ||||
| The BCMA devices include the brcmnand controller but they do not wire up | ||||
| any interrupt line, allow the main interrupt to be optional and update | ||||
| the completion path to also check for the lack of an interrupt line. | ||||
|  | ||||
| Signed-off-by: Florian Fainelli <f.fainelli@gmail.com> | ||||
| --- | ||||
|  drivers/mtd/nand/raw/brcmnand/brcmnand.c | 52 +++++++++++------------- | ||||
|  1 file changed, 24 insertions(+), 28 deletions(-) | ||||
|  | ||||
| --- a/drivers/mtd/nand/raw/brcmnand/brcmnand.c | ||||
| +++ b/drivers/mtd/nand/raw/brcmnand/brcmnand.c | ||||
| @@ -216,7 +216,7 @@ struct brcmnand_controller { | ||||
|  	void __iomem		*nand_base; | ||||
|  	void __iomem		*nand_fc; /* flash cache */ | ||||
|  	void __iomem		*flash_dma_base; | ||||
| -	unsigned int		irq; | ||||
| +	int			irq; | ||||
|  	unsigned int		dma_irq; | ||||
|  	int			nand_version; | ||||
|   | ||||
| @@ -1590,7 +1590,7 @@ static bool brcmstb_nand_wait_for_comple | ||||
|  	bool err = false; | ||||
|  	int sts; | ||||
|   | ||||
| -	if (mtd->oops_panic_write) { | ||||
| +	if (mtd->oops_panic_write || ctrl->irq < 0) { | ||||
|  		/* switch to interrupt polling and PIO mode */ | ||||
|  		disable_ctrl_irqs(ctrl); | ||||
|  		sts = bcmnand_ctrl_poll_status(ctrl, NAND_CTRL_RDY, | ||||
| @@ -3095,33 +3095,29 @@ int brcmnand_probe(struct platform_devic | ||||
|  	} | ||||
|   | ||||
|  	/* IRQ */ | ||||
| -	ctrl->irq = platform_get_irq(pdev, 0); | ||||
| -	if ((int)ctrl->irq < 0) { | ||||
| -		dev_err(dev, "no IRQ defined\n"); | ||||
| -		ret = -ENODEV; | ||||
| -		goto err; | ||||
| -	} | ||||
| - | ||||
| -	/* | ||||
| -	 * Some SoCs integrate this controller (e.g., its interrupt bits) in | ||||
| -	 * interesting ways | ||||
| -	 */ | ||||
| -	if (soc) { | ||||
| -		ret = devm_request_irq(dev, ctrl->irq, brcmnand_irq, 0, | ||||
| -				       DRV_NAME, ctrl); | ||||
| - | ||||
| -		/* Enable interrupt */ | ||||
| -		ctrl->soc->ctlrdy_ack(ctrl->soc); | ||||
| -		ctrl->soc->ctlrdy_set_enabled(ctrl->soc, true); | ||||
| -	} else { | ||||
| -		/* Use standard interrupt infrastructure */ | ||||
| -		ret = devm_request_irq(dev, ctrl->irq, brcmnand_ctlrdy_irq, 0, | ||||
| -				       DRV_NAME, ctrl); | ||||
| -	} | ||||
| -	if (ret < 0) { | ||||
| -		dev_err(dev, "can't allocate IRQ %d: error %d\n", | ||||
| -			ctrl->irq, ret); | ||||
| -		goto err; | ||||
| +	ctrl->irq = platform_get_irq_optional(pdev, 0); | ||||
| +	if (ctrl->irq > 0) { | ||||
| +		/* | ||||
| +		 * Some SoCs integrate this controller (e.g., its interrupt bits) in | ||||
| +		 * interesting ways | ||||
| +		 */ | ||||
| +		if (soc) { | ||||
| +			ret = devm_request_irq(dev, ctrl->irq, brcmnand_irq, 0, | ||||
| +					       DRV_NAME, ctrl); | ||||
| + | ||||
| +			/* Enable interrupt */ | ||||
| +			ctrl->soc->ctlrdy_ack(ctrl->soc); | ||||
| +			ctrl->soc->ctlrdy_set_enabled(ctrl->soc, true); | ||||
| +		} else { | ||||
| +			/* Use standard interrupt infrastructure */ | ||||
| +			ret = devm_request_irq(dev, ctrl->irq, brcmnand_ctlrdy_irq, 0, | ||||
| +					       DRV_NAME, ctrl); | ||||
| +		} | ||||
| +		if (ret < 0) { | ||||
| +			dev_err(dev, "can't allocate IRQ %d: error %d\n", | ||||
| +				ctrl->irq, ret); | ||||
| +			goto err; | ||||
| +		} | ||||
|  	} | ||||
|   | ||||
|  	for_each_available_child_of_node(dn, child) { | ||||
| @@ -0,0 +1,115 @@ | ||||
| From: Florian Fainelli <f.fainelli@gmail.com> | ||||
| Subject: [PATCH v3 6/9] mtd: rawnand: brcmnand: Add platform data structure for BCMA | ||||
| Date: Fri, 07 Jan 2022 10:46:11 -0800 | ||||
| Content-Type: text/plain; charset="utf-8" | ||||
|  | ||||
| Update the BCMA's chipcommon nand flash driver to detect which | ||||
| chip-select is used and pass that information via platform data to the | ||||
| brcmnand driver. Make sure that the brcmnand platform data structure is | ||||
| always at the beginning of the platform data of the "nflash" device | ||||
| created by BCMA to allow brcmnand to safely de-reference it. | ||||
|  | ||||
| Signed-off-by: Florian Fainelli <f.fainelli@gmail.com> | ||||
| --- | ||||
|  MAINTAINERS                                 |  1 + | ||||
|  drivers/bcma/driver_chipcommon_nflash.c     | 20 +++++++++++++++++++- | ||||
|  include/linux/bcma/bcma_driver_chipcommon.h |  5 +++++ | ||||
|  include/linux/platform_data/brcmnand.h      | 12 ++++++++++++ | ||||
|  4 files changed, 37 insertions(+), 1 deletion(-) | ||||
|  create mode 100644 include/linux/platform_data/brcmnand.h | ||||
|  | ||||
| --- a/MAINTAINERS | ||||
| +++ b/MAINTAINERS | ||||
| @@ -3712,6 +3712,7 @@ L:	linux-mtd@lists.infradead.org | ||||
|  L:	bcm-kernel-feedback-list@broadcom.com | ||||
|  S:	Maintained | ||||
|  F:	drivers/mtd/nand/raw/brcmnand/ | ||||
| +F:	include/linux/platform_data/brcmnand.h | ||||
|   | ||||
|  BROADCOM SYSTEMPORT ETHERNET DRIVER | ||||
|  M:	Florian Fainelli <f.fainelli@gmail.com> | ||||
| --- a/drivers/bcma/driver_chipcommon_nflash.c | ||||
| +++ b/drivers/bcma/driver_chipcommon_nflash.c | ||||
| @@ -7,18 +7,28 @@ | ||||
|   | ||||
|  #include "bcma_private.h" | ||||
|   | ||||
| +#include <linux/bitops.h> | ||||
|  #include <linux/platform_device.h> | ||||
| +#include <linux/platform_data/brcmnand.h> | ||||
|  #include <linux/bcma/bcma.h> | ||||
|   | ||||
| +/* Alternate NAND controller driver name in order to allow both bcm47xxnflash | ||||
| + * and bcma_brcmnand to be built into the same kernel image. | ||||
| + */ | ||||
| +static const char *bcma_nflash_alt_name = "bcma_brcmnand"; | ||||
| + | ||||
|  struct platform_device bcma_nflash_dev = { | ||||
|  	.name		= "bcma_nflash", | ||||
|  	.num_resources	= 0, | ||||
|  }; | ||||
|   | ||||
| +static const char *probes[] = { "bcm47xxpart", NULL }; | ||||
| + | ||||
|  /* Initialize NAND flash access */ | ||||
|  int bcma_nflash_init(struct bcma_drv_cc *cc) | ||||
|  { | ||||
|  	struct bcma_bus *bus = cc->core->bus; | ||||
| +	u32 reg; | ||||
|   | ||||
|  	if (bus->chipinfo.id != BCMA_CHIP_ID_BCM4706 && | ||||
|  	    cc->core->id.rev != 38) { | ||||
| @@ -33,8 +43,16 @@ int bcma_nflash_init(struct bcma_drv_cc | ||||
|   | ||||
|  	cc->nflash.present = true; | ||||
|  	if (cc->core->id.rev == 38 && | ||||
| -	    (cc->status & BCMA_CC_CHIPST_5357_NAND_BOOT)) | ||||
| +	    (cc->status & BCMA_CC_CHIPST_5357_NAND_BOOT)) { | ||||
|  		cc->nflash.boot = true; | ||||
| +		/* Determine the chip select that is being used */ | ||||
| +		reg = bcma_cc_read32(cc, BCMA_CC_NAND_CS_NAND_SELECT) & 0xff; | ||||
| +		cc->nflash.brcmnand_info.chip_select = ffs(reg) - 1; | ||||
| +		cc->nflash.brcmnand_info.part_probe_types = probes; | ||||
| +		cc->nflash.brcmnand_info.ecc_stepsize = 512; | ||||
| +		cc->nflash.brcmnand_info.ecc_strength = 1; | ||||
| +		bcma_nflash_dev.name = bcma_nflash_alt_name; | ||||
| +	} | ||||
|   | ||||
|  	/* Prepare platform device, but don't register it yet. It's too early, | ||||
|  	 * malloc (required by device_private_init) is not available yet. */ | ||||
| --- a/include/linux/bcma/bcma_driver_chipcommon.h | ||||
| +++ b/include/linux/bcma/bcma_driver_chipcommon.h | ||||
| @@ -3,6 +3,7 @@ | ||||
|  #define LINUX_BCMA_DRIVER_CC_H_ | ||||
|   | ||||
|  #include <linux/platform_device.h> | ||||
| +#include <linux/platform_data/brcmnand.h> | ||||
|  #include <linux/gpio.h> | ||||
|   | ||||
|  /** ChipCommon core registers. **/ | ||||
| @@ -599,6 +600,10 @@ struct bcma_sflash { | ||||
|   | ||||
|  #ifdef CONFIG_BCMA_NFLASH | ||||
|  struct bcma_nflash { | ||||
| +	/* Must be the fist member for the brcmnand driver to | ||||
| +	 * de-reference that structure. | ||||
| +	 */ | ||||
| +	struct brcmnand_platform_data brcmnand_info; | ||||
|  	bool present; | ||||
|  	bool boot;		/* This is the flash the SoC boots from */ | ||||
|  }; | ||||
| --- /dev/null | ||||
| +++ b/include/linux/platform_data/brcmnand.h | ||||
| @@ -0,0 +1,12 @@ | ||||
| +/* SPDX-License-Identifier: GPL-2.0-only */ | ||||
| +#ifndef BRCMNAND_PLAT_DATA_H | ||||
| +#define BRCMNAND_PLAT_DATA_H | ||||
| + | ||||
| +struct brcmnand_platform_data { | ||||
| +	int	chip_select; | ||||
| +	const char * const *part_probe_types; | ||||
| +	unsigned int ecc_stepsize; | ||||
| +	unsigned int ecc_strength; | ||||
| +}; | ||||
| + | ||||
| +#endif /* BRCMNAND_PLAT_DATA_H */ | ||||
| @@ -0,0 +1,124 @@ | ||||
| From: Florian Fainelli <f.fainelli@gmail.com> | ||||
| Subject: [PATCH v3 7/9] mtd: rawnand: brcmnand: Allow platform data instantation | ||||
| Date: Fri, 07 Jan 2022 10:46:12 -0800 | ||||
| Content-Type: text/plain; charset="utf-8" | ||||
|  | ||||
| Make use of the recently refactored code in brcmnand_init_cs() and | ||||
| derive the chip-select from the platform data that is supplied. Update | ||||
| the various code paths to avoid relying on possibly non-existent | ||||
| resources, too. | ||||
|  | ||||
| Signed-off-by: Florian Fainelli <f.fainelli@gmail.com> | ||||
| --- | ||||
|  drivers/mtd/nand/raw/brcmnand/brcmnand.c | 45 ++++++++++++++++++------ | ||||
|  1 file changed, 35 insertions(+), 10 deletions(-) | ||||
|  | ||||
| --- a/drivers/mtd/nand/raw/brcmnand/brcmnand.c | ||||
| +++ b/drivers/mtd/nand/raw/brcmnand/brcmnand.c | ||||
| @@ -9,6 +9,7 @@ | ||||
|  #include <linux/delay.h> | ||||
|  #include <linux/device.h> | ||||
|  #include <linux/platform_device.h> | ||||
| +#include <linux/platform_data/brcmnand.h> | ||||
|  #include <linux/err.h> | ||||
|  #include <linux/completion.h> | ||||
|  #include <linux/interrupt.h> | ||||
| @@ -2719,7 +2720,8 @@ static const struct nand_controller_ops | ||||
|  	.attach_chip = brcmnand_attach_chip, | ||||
|  }; | ||||
|   | ||||
| -static int brcmnand_init_cs(struct brcmnand_host *host) | ||||
| +static int brcmnand_init_cs(struct brcmnand_host *host, | ||||
| +			    const char * const *part_probe_types) | ||||
|  { | ||||
|  	struct brcmnand_controller *ctrl = host->ctrl; | ||||
|  	struct device *dev = ctrl->dev; | ||||
| @@ -2772,7 +2774,7 @@ static int brcmnand_init_cs(struct brcmn | ||||
|  	if (ret) | ||||
|  		return ret; | ||||
|   | ||||
| -	ret = mtd_device_register(mtd, NULL, 0); | ||||
| +	ret = mtd_device_parse_register(mtd, part_probe_types, NULL, NULL, 0); | ||||
|  	if (ret) | ||||
|  		nand_cleanup(chip); | ||||
|   | ||||
| @@ -2941,17 +2943,15 @@ static int brcmnand_edu_setup(struct pla | ||||
|   | ||||
|  int brcmnand_probe(struct platform_device *pdev, struct brcmnand_soc *soc) | ||||
|  { | ||||
| +	struct brcmnand_platform_data *pd = dev_get_platdata(&pdev->dev); | ||||
|  	struct device *dev = &pdev->dev; | ||||
|  	struct device_node *dn = dev->of_node, *child; | ||||
|  	struct brcmnand_controller *ctrl; | ||||
| +	struct brcmnand_host *host; | ||||
|  	struct resource *res; | ||||
|  	int ret; | ||||
|   | ||||
| -	/* We only support device-tree instantiation */ | ||||
| -	if (!dn) | ||||
| -		return -ENODEV; | ||||
| - | ||||
| -	if (!of_match_node(brcmnand_of_match, dn)) | ||||
| +	if (dn && !of_match_node(brcmnand_of_match, dn)) | ||||
|  		return -ENODEV; | ||||
|   | ||||
|  	ctrl = devm_kzalloc(dev, sizeof(*ctrl), GFP_KERNEL); | ||||
| @@ -2978,7 +2978,7 @@ int brcmnand_probe(struct platform_devic | ||||
|  	/* NAND register range */ | ||||
|  	res = platform_get_resource(pdev, IORESOURCE_MEM, 0); | ||||
|  	ctrl->nand_base = devm_ioremap_resource(dev, res); | ||||
| -	if (IS_ERR(ctrl->nand_base)) | ||||
| +	if (IS_ERR(ctrl->nand_base) && !brcmnand_soc_has_ops(soc)) | ||||
|  		return PTR_ERR(ctrl->nand_base); | ||||
|   | ||||
|  	/* Enable clock before using NAND registers */ | ||||
| @@ -3122,7 +3122,6 @@ int brcmnand_probe(struct platform_devic | ||||
|   | ||||
|  	for_each_available_child_of_node(dn, child) { | ||||
|  		if (of_device_is_compatible(child, "brcm,nandcs")) { | ||||
| -			struct brcmnand_host *host; | ||||
|   | ||||
|  			host = devm_kzalloc(dev, sizeof(*host), GFP_KERNEL); | ||||
|  			if (!host) { | ||||
| @@ -3142,7 +3141,7 @@ int brcmnand_probe(struct platform_devic | ||||
|   | ||||
|  			nand_set_flash_node(&host->chip, child); | ||||
|   | ||||
| -			ret = brcmnand_init_cs(host); | ||||
| +			ret = brcmnand_init_cs(host, NULL); | ||||
|  			if (ret) { | ||||
|  				devm_kfree(dev, host); | ||||
|  				continue; /* Try all chip-selects */ | ||||
| @@ -3152,6 +3151,32 @@ int brcmnand_probe(struct platform_devic | ||||
|  		} | ||||
|  	} | ||||
|   | ||||
| +	if (!list_empty(&ctrl->host_list)) | ||||
| +		return 0; | ||||
| + | ||||
| +	if (!pd) { | ||||
| +		ret = -ENODEV; | ||||
| +		goto err; | ||||
| +	} | ||||
| + | ||||
| +	/* If we got there we must have been probing via platform data */ | ||||
| +	host = devm_kzalloc(dev, sizeof(*host), GFP_KERNEL); | ||||
| +	if (!host) { | ||||
| +		ret = -ENOMEM; | ||||
| +		goto err; | ||||
| +	} | ||||
| +	host->pdev = pdev; | ||||
| +	host->ctrl = ctrl; | ||||
| +	host->cs = pd->chip_select; | ||||
| +	host->chip.ecc.size = pd->ecc_stepsize; | ||||
| +	host->chip.ecc.strength = pd->ecc_strength; | ||||
| + | ||||
| +	ret = brcmnand_init_cs(host, pd->part_probe_types); | ||||
| +	if (ret) | ||||
| +		goto err; | ||||
| + | ||||
| +	list_add_tail(&host->node, &ctrl->host_list); | ||||
| + | ||||
|  	/* No chip-selects could initialize properly */ | ||||
|  	if (list_empty(&ctrl->host_list)) { | ||||
|  		ret = -ENODEV; | ||||
| @@ -0,0 +1,29 @@ | ||||
| From: Florian Fainelli <f.fainelli@gmail.com> | ||||
| Subject: [PATCH v3 8/9] mtd: rawnand: brcmnand: BCMA controller uses command shift of 0 | ||||
| Date: Fri, 07 Jan 2022 10:46:13 -0800 | ||||
| Content-Type: text/plain; charset="utf-8" | ||||
|  | ||||
| For some odd and unexplained reason the BCMA NAND controller, albeit | ||||
| revision 3.4 uses a command shift of 0 instead of 24 as it should be, | ||||
| quirk that. | ||||
|  | ||||
| Signed-off-by: Florian Fainelli <f.fainelli@gmail.com> | ||||
| --- | ||||
|  drivers/mtd/nand/raw/brcmnand/brcmnand.c | 6 ++++++ | ||||
|  1 file changed, 6 insertions(+) | ||||
|  | ||||
| --- a/drivers/mtd/nand/raw/brcmnand/brcmnand.c | ||||
| +++ b/drivers/mtd/nand/raw/brcmnand/brcmnand.c | ||||
| @@ -913,6 +913,12 @@ static void brcmnand_wr_corr_thresh(stru | ||||
|   | ||||
|  static inline int brcmnand_cmd_shift(struct brcmnand_controller *ctrl) | ||||
|  { | ||||
| +	/* Kludge for the BCMA-based NAND controller which does not actually | ||||
| +	 * shift the command | ||||
| +	 */ | ||||
| +	if (ctrl->nand_version == 0x0304 && brcmnand_non_mmio_ops(ctrl)) | ||||
| +		return 0; | ||||
| + | ||||
|  	if (ctrl->nand_version < 0x0602) | ||||
|  		return 24; | ||||
|  	return 0; | ||||
| @@ -0,0 +1,201 @@ | ||||
| From: Florian Fainelli <f.fainelli@gmail.com> | ||||
| Subject: [PATCH v3 9/9] mtd: rawnand: brcmnand: Add BCMA shim | ||||
| Date: Fri, 07 Jan 2022 10:46:14 -0800 | ||||
| Content-Type: text/plain; charset="utf-8" | ||||
|  | ||||
| Add a BCMA shim to allow us to register the brcmnand driver using the | ||||
| BCMA bus which provides indirect memory mapped access to SoC registers. | ||||
|  | ||||
| There are a number of registers that need to be byte swapped because | ||||
| they are natively big endian, coming directly from the NAND chip, and | ||||
| there is no bus interface unlike the iProc or STB platforms that | ||||
| performs the byte swapping for us. | ||||
|  | ||||
| Signed-off-by: Florian Fainelli <f.fainelli@gmail.com> | ||||
| --- | ||||
|  drivers/mtd/nand/raw/Kconfig              |  13 +++ | ||||
|  drivers/mtd/nand/raw/brcmnand/Makefile    |   2 + | ||||
|  drivers/mtd/nand/raw/brcmnand/bcma_nand.c | 132 ++++++++++++++++++++++ | ||||
|  drivers/mtd/nand/raw/brcmnand/brcmnand.c  |   4 + | ||||
|  4 files changed, 151 insertions(+) | ||||
|  create mode 100644 drivers/mtd/nand/raw/brcmnand/bcma_nand.c | ||||
|  | ||||
| --- a/drivers/mtd/nand/raw/Kconfig | ||||
| +++ b/drivers/mtd/nand/raw/Kconfig | ||||
| @@ -236,6 +236,19 @@ config MTD_NAND_BRCMNAND | ||||
|  	  originally designed for Set-Top Box but is used on various BCM7xxx, | ||||
|  	  BCM3xxx, BCM63xxx, iProc/Cygnus and more. | ||||
|   | ||||
| +if MTD_NAND_BRCMNAND | ||||
| + | ||||
| +config MTD_NAND_BRCMNAND_BCMA | ||||
| +	tristate "Broadcom BCMA NAND controller" | ||||
| +	depends on BCMA_NFLASH | ||||
| +	depends on BCMA | ||||
| +	help | ||||
| +	  Enables the BRCMNAND controller over BCMA on BCM47186/BCM5358 SoCs. | ||||
| +	  The glue driver will take care of performing the low-level I/O | ||||
| +	  operations to interface the BRCMNAND controller over the BCMA bus. | ||||
| + | ||||
| +endif # MTD_NAND_BRCMNAND | ||||
| + | ||||
|  config MTD_NAND_BCM47XXNFLASH | ||||
|  	tristate "BCM4706 BCMA NAND controller" | ||||
|  	depends on BCMA_NFLASH | ||||
| --- a/drivers/mtd/nand/raw/brcmnand/Makefile | ||||
| +++ b/drivers/mtd/nand/raw/brcmnand/Makefile | ||||
| @@ -6,3 +6,5 @@ obj-$(CONFIG_MTD_NAND_BRCMNAND)		+= bcm6 | ||||
|  obj-$(CONFIG_MTD_NAND_BRCMNAND)		+= bcm6368_nand.o | ||||
|  obj-$(CONFIG_MTD_NAND_BRCMNAND)		+= brcmstb_nand.o | ||||
|  obj-$(CONFIG_MTD_NAND_BRCMNAND)		+= brcmnand.o | ||||
| + | ||||
| +obj-$(CONFIG_MTD_NAND_BRCMNAND_BCMA)	+= bcma_nand.o | ||||
| --- /dev/null | ||||
| +++ b/drivers/mtd/nand/raw/brcmnand/bcma_nand.c | ||||
| @@ -0,0 +1,132 @@ | ||||
| +// SPDX-License-Identifier: GPL-2.0-only | ||||
| +/* | ||||
| + * Copyright © 2021 Broadcom | ||||
| + */ | ||||
| +#include <linux/bcma/bcma.h> | ||||
| +#include <linux/bcma/bcma_driver_chipcommon.h> | ||||
| +#include <linux/device.h> | ||||
| +#include <linux/module.h> | ||||
| +#include <linux/platform_device.h> | ||||
| + | ||||
| +#include "brcmnand.h" | ||||
| + | ||||
| +struct brcmnand_bcma_soc { | ||||
| +	struct brcmnand_soc soc; | ||||
| +	struct bcma_drv_cc *cc; | ||||
| +}; | ||||
| + | ||||
| +static inline bool brcmnand_bcma_needs_swapping(u32 offset) | ||||
| +{ | ||||
| +	switch (offset) { | ||||
| +	case BCMA_CC_NAND_SPARE_RD0: | ||||
| +	case BCMA_CC_NAND_SPARE_RD4: | ||||
| +	case BCMA_CC_NAND_SPARE_RD8: | ||||
| +	case BCMA_CC_NAND_SPARE_RD12: | ||||
| +	case BCMA_CC_NAND_SPARE_WR0: | ||||
| +	case BCMA_CC_NAND_SPARE_WR4: | ||||
| +	case BCMA_CC_NAND_SPARE_WR8: | ||||
| +	case BCMA_CC_NAND_SPARE_WR12: | ||||
| +	case BCMA_CC_NAND_DEVID: | ||||
| +	case BCMA_CC_NAND_DEVID_X: | ||||
| +	case BCMA_CC_NAND_SPARE_RD16: | ||||
| +	case BCMA_CC_NAND_SPARE_RD20: | ||||
| +	case BCMA_CC_NAND_SPARE_RD24: | ||||
| +	case BCMA_CC_NAND_SPARE_RD28: | ||||
| +		return true; | ||||
| +	} | ||||
| + | ||||
| +	return false; | ||||
| +} | ||||
| + | ||||
| +static inline struct brcmnand_bcma_soc *to_bcma_soc(struct brcmnand_soc *soc) | ||||
| +{ | ||||
| +	return container_of(soc, struct brcmnand_bcma_soc, soc); | ||||
| +} | ||||
| + | ||||
| +static u32 brcmnand_bcma_read_reg(struct brcmnand_soc *soc, u32 offset) | ||||
| +{ | ||||
| +	struct brcmnand_bcma_soc *sc = to_bcma_soc(soc); | ||||
| +	u32 val; | ||||
| + | ||||
| +	/* Offset into the NAND block and deal with the flash cache separately */ | ||||
| +	if (offset == BRCMNAND_NON_MMIO_FC_ADDR) | ||||
| +		offset = BCMA_CC_NAND_CACHE_DATA; | ||||
| +	else | ||||
| +		offset += BCMA_CC_NAND_REVISION; | ||||
| + | ||||
| +	val = bcma_cc_read32(sc->cc, offset); | ||||
| + | ||||
| +	/* Swap if necessary */ | ||||
| +	if (brcmnand_bcma_needs_swapping(offset)) | ||||
| +		val = be32_to_cpu(val); | ||||
| +	return val; | ||||
| +} | ||||
| + | ||||
| +static void brcmnand_bcma_write_reg(struct brcmnand_soc *soc, u32 val, | ||||
| +				    u32 offset) | ||||
| +{ | ||||
| +	struct brcmnand_bcma_soc *sc = to_bcma_soc(soc); | ||||
| + | ||||
| +	/* Offset into the NAND block */ | ||||
| +	if (offset == BRCMNAND_NON_MMIO_FC_ADDR) | ||||
| +		offset = BCMA_CC_NAND_CACHE_DATA; | ||||
| +	else | ||||
| +		offset += BCMA_CC_NAND_REVISION; | ||||
| + | ||||
| +	/* Swap if necessary */ | ||||
| +	if (brcmnand_bcma_needs_swapping(offset)) | ||||
| +		val = cpu_to_be32(val); | ||||
| + | ||||
| +	bcma_cc_write32(sc->cc, offset, val); | ||||
| +} | ||||
| + | ||||
| +static struct brcmnand_io_ops brcmnand_bcma_io_ops = { | ||||
| +	.read_reg	= brcmnand_bcma_read_reg, | ||||
| +	.write_reg	= brcmnand_bcma_write_reg, | ||||
| +}; | ||||
| + | ||||
| +static void brcmnand_bcma_prepare_data_bus(struct brcmnand_soc *soc, bool prepare, | ||||
| +					   bool is_param) | ||||
| +{ | ||||
| +	struct brcmnand_bcma_soc *sc = to_bcma_soc(soc); | ||||
| + | ||||
| +	/* Reset the cache address to ensure we are already accessing the | ||||
| +	 * beginning of a sub-page. | ||||
| +	 */ | ||||
| +	bcma_cc_write32(sc->cc, BCMA_CC_NAND_CACHE_ADDR, 0); | ||||
| +} | ||||
| + | ||||
| +static int brcmnand_bcma_nand_probe(struct platform_device *pdev) | ||||
| +{ | ||||
| +	struct bcma_nflash *nflash = dev_get_platdata(&pdev->dev); | ||||
| +	struct brcmnand_bcma_soc *soc; | ||||
| + | ||||
| +	soc = devm_kzalloc(&pdev->dev, sizeof(*soc), GFP_KERNEL); | ||||
| +	if (!soc) | ||||
| +		return -ENOMEM; | ||||
| + | ||||
| +	soc->cc = container_of(nflash, struct bcma_drv_cc, nflash); | ||||
| +	soc->soc.prepare_data_bus = brcmnand_bcma_prepare_data_bus; | ||||
| +	soc->soc.ops = &brcmnand_bcma_io_ops; | ||||
| + | ||||
| +	if (soc->cc->core->bus->chipinfo.id == BCMA_CHIP_ID_BCM4706) { | ||||
| +		dev_err(&pdev->dev, "Use bcm47xxnflash for 4706!\n"); | ||||
| +		return -ENODEV; | ||||
| +	} | ||||
| + | ||||
| +	return brcmnand_probe(pdev, &soc->soc); | ||||
| +} | ||||
| + | ||||
| +static struct platform_driver brcmnand_bcma_nand_driver = { | ||||
| +	.probe			= brcmnand_bcma_nand_probe, | ||||
| +	.remove			= brcmnand_remove, | ||||
| +	.driver = { | ||||
| +		.name		= "bcma_brcmnand", | ||||
| +		.pm		= &brcmnand_pm_ops, | ||||
| +	} | ||||
| +}; | ||||
| +module_platform_driver(brcmnand_bcma_nand_driver); | ||||
| + | ||||
| +MODULE_LICENSE("GPL v2"); | ||||
| +MODULE_AUTHOR("Broadcom"); | ||||
| +MODULE_DESCRIPTION("NAND controller driver glue for BCMA chips"); | ||||
| --- a/drivers/mtd/nand/raw/brcmnand/brcmnand.c | ||||
| +++ b/drivers/mtd/nand/raw/brcmnand/brcmnand.c | ||||
| @@ -595,7 +595,11 @@ enum { | ||||
|   | ||||
|  static inline bool brcmnand_non_mmio_ops(struct brcmnand_controller *ctrl) | ||||
|  { | ||||
| +#if IS_ENABLED(CONFIG_MTD_NAND_BRCMNAND_BCMA) | ||||
|  	return static_branch_unlikely(&brcmnand_soc_has_ops_key); | ||||
| +#else | ||||
| +	return false; | ||||
| +#endif | ||||
|  } | ||||
|   | ||||
|  static inline u32 nand_readreg(struct brcmnand_controller *ctrl, u32 offs) | ||||
		Reference in New Issue
	
	Block a user
	 Florian Fainelli
					Florian Fainelli