162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-only
262306a36Sopenharmony_ci#include <linux/kernel.h>
362306a36Sopenharmony_ci#include <linux/module.h>
462306a36Sopenharmony_ci#include <linux/slab.h>
562306a36Sopenharmony_ci#include <linux/delay.h>
662306a36Sopenharmony_ci#include <linux/ioport.h>
762306a36Sopenharmony_ci#include <linux/mtd/mtd.h>
862306a36Sopenharmony_ci#include <linux/platform_device.h>
962306a36Sopenharmony_ci#include <linux/bcma/bcma.h>
1062306a36Sopenharmony_ci
1162306a36Sopenharmony_ci#include "bcm47xxsflash.h"
1262306a36Sopenharmony_ci
1362306a36Sopenharmony_ciMODULE_LICENSE("GPL");
1462306a36Sopenharmony_ciMODULE_DESCRIPTION("Serial flash driver for BCMA bus");
1562306a36Sopenharmony_ci
1662306a36Sopenharmony_cistatic const char * const probes[] = { "bcm47xxpart", NULL };
1762306a36Sopenharmony_ci
1862306a36Sopenharmony_ci/**************************************************
1962306a36Sopenharmony_ci * Various helpers
2062306a36Sopenharmony_ci **************************************************/
2162306a36Sopenharmony_ci
2262306a36Sopenharmony_cistatic void bcm47xxsflash_cmd(struct bcm47xxsflash *b47s, u32 opcode)
2362306a36Sopenharmony_ci{
2462306a36Sopenharmony_ci	int i;
2562306a36Sopenharmony_ci
2662306a36Sopenharmony_ci	b47s->cc_write(b47s, BCMA_CC_FLASHCTL, BCMA_CC_FLASHCTL_START | opcode);
2762306a36Sopenharmony_ci	for (i = 0; i < 1000; i++) {
2862306a36Sopenharmony_ci		if (!(b47s->cc_read(b47s, BCMA_CC_FLASHCTL) &
2962306a36Sopenharmony_ci		      BCMA_CC_FLASHCTL_BUSY))
3062306a36Sopenharmony_ci			return;
3162306a36Sopenharmony_ci		cpu_relax();
3262306a36Sopenharmony_ci	}
3362306a36Sopenharmony_ci	pr_err("Control command failed (timeout)!\n");
3462306a36Sopenharmony_ci}
3562306a36Sopenharmony_ci
3662306a36Sopenharmony_cistatic int bcm47xxsflash_poll(struct bcm47xxsflash *b47s, int timeout)
3762306a36Sopenharmony_ci{
3862306a36Sopenharmony_ci	unsigned long deadline = jiffies + timeout;
3962306a36Sopenharmony_ci
4062306a36Sopenharmony_ci	do {
4162306a36Sopenharmony_ci		switch (b47s->type) {
4262306a36Sopenharmony_ci		case BCM47XXSFLASH_TYPE_ST:
4362306a36Sopenharmony_ci			bcm47xxsflash_cmd(b47s, OPCODE_ST_RDSR);
4462306a36Sopenharmony_ci			if (!(b47s->cc_read(b47s, BCMA_CC_FLASHDATA) &
4562306a36Sopenharmony_ci			      SR_ST_WIP))
4662306a36Sopenharmony_ci				return 0;
4762306a36Sopenharmony_ci			break;
4862306a36Sopenharmony_ci		case BCM47XXSFLASH_TYPE_ATMEL:
4962306a36Sopenharmony_ci			bcm47xxsflash_cmd(b47s, OPCODE_AT_STATUS);
5062306a36Sopenharmony_ci			if (b47s->cc_read(b47s, BCMA_CC_FLASHDATA) &
5162306a36Sopenharmony_ci			    SR_AT_READY)
5262306a36Sopenharmony_ci				return 0;
5362306a36Sopenharmony_ci			break;
5462306a36Sopenharmony_ci		}
5562306a36Sopenharmony_ci
5662306a36Sopenharmony_ci		cpu_relax();
5762306a36Sopenharmony_ci		udelay(1);
5862306a36Sopenharmony_ci	} while (!time_after_eq(jiffies, deadline));
5962306a36Sopenharmony_ci
6062306a36Sopenharmony_ci	pr_err("Timeout waiting for flash to be ready!\n");
6162306a36Sopenharmony_ci
6262306a36Sopenharmony_ci	return -EBUSY;
6362306a36Sopenharmony_ci}
6462306a36Sopenharmony_ci
6562306a36Sopenharmony_ci/**************************************************
6662306a36Sopenharmony_ci * MTD ops
6762306a36Sopenharmony_ci **************************************************/
6862306a36Sopenharmony_ci
6962306a36Sopenharmony_cistatic int bcm47xxsflash_erase(struct mtd_info *mtd, struct erase_info *erase)
7062306a36Sopenharmony_ci{
7162306a36Sopenharmony_ci	struct bcm47xxsflash *b47s = mtd->priv;
7262306a36Sopenharmony_ci
7362306a36Sopenharmony_ci	switch (b47s->type) {
7462306a36Sopenharmony_ci	case BCM47XXSFLASH_TYPE_ST:
7562306a36Sopenharmony_ci		bcm47xxsflash_cmd(b47s, OPCODE_ST_WREN);
7662306a36Sopenharmony_ci		b47s->cc_write(b47s, BCMA_CC_FLASHADDR, erase->addr);
7762306a36Sopenharmony_ci		/* Newer flashes have "sub-sectors" which can be erased
7862306a36Sopenharmony_ci		 * independently with a new command: ST_SSE. The ST_SE command
7962306a36Sopenharmony_ci		 * erases 64KB just as before.
8062306a36Sopenharmony_ci		 */
8162306a36Sopenharmony_ci		if (b47s->blocksize < (64 * 1024))
8262306a36Sopenharmony_ci			bcm47xxsflash_cmd(b47s, OPCODE_ST_SSE);
8362306a36Sopenharmony_ci		else
8462306a36Sopenharmony_ci			bcm47xxsflash_cmd(b47s, OPCODE_ST_SE);
8562306a36Sopenharmony_ci		break;
8662306a36Sopenharmony_ci	case BCM47XXSFLASH_TYPE_ATMEL:
8762306a36Sopenharmony_ci		b47s->cc_write(b47s, BCMA_CC_FLASHADDR, erase->addr << 1);
8862306a36Sopenharmony_ci		bcm47xxsflash_cmd(b47s, OPCODE_AT_PAGE_ERASE);
8962306a36Sopenharmony_ci		break;
9062306a36Sopenharmony_ci	}
9162306a36Sopenharmony_ci
9262306a36Sopenharmony_ci	return bcm47xxsflash_poll(b47s, HZ);
9362306a36Sopenharmony_ci}
9462306a36Sopenharmony_ci
9562306a36Sopenharmony_cistatic int bcm47xxsflash_read(struct mtd_info *mtd, loff_t from, size_t len,
9662306a36Sopenharmony_ci			      size_t *retlen, u_char *buf)
9762306a36Sopenharmony_ci{
9862306a36Sopenharmony_ci	struct bcm47xxsflash *b47s = mtd->priv;
9962306a36Sopenharmony_ci	size_t orig_len = len;
10062306a36Sopenharmony_ci
10162306a36Sopenharmony_ci	/* Check address range */
10262306a36Sopenharmony_ci	if ((from + len) > mtd->size)
10362306a36Sopenharmony_ci		return -EINVAL;
10462306a36Sopenharmony_ci
10562306a36Sopenharmony_ci	/* Read as much as possible using fast MMIO window */
10662306a36Sopenharmony_ci	if (from < BCM47XXSFLASH_WINDOW_SZ) {
10762306a36Sopenharmony_ci		size_t memcpy_len;
10862306a36Sopenharmony_ci
10962306a36Sopenharmony_ci		memcpy_len = min(len, (size_t)(BCM47XXSFLASH_WINDOW_SZ - from));
11062306a36Sopenharmony_ci		memcpy_fromio(buf, b47s->window + from, memcpy_len);
11162306a36Sopenharmony_ci		from += memcpy_len;
11262306a36Sopenharmony_ci		len -= memcpy_len;
11362306a36Sopenharmony_ci		buf += memcpy_len;
11462306a36Sopenharmony_ci	}
11562306a36Sopenharmony_ci
11662306a36Sopenharmony_ci	/* Use indirect access for content out of the window */
11762306a36Sopenharmony_ci	for (; len; len--) {
11862306a36Sopenharmony_ci		b47s->cc_write(b47s, BCMA_CC_FLASHADDR, from++);
11962306a36Sopenharmony_ci		bcm47xxsflash_cmd(b47s, OPCODE_ST_READ4B);
12062306a36Sopenharmony_ci		*buf++ = b47s->cc_read(b47s, BCMA_CC_FLASHDATA);
12162306a36Sopenharmony_ci	}
12262306a36Sopenharmony_ci
12362306a36Sopenharmony_ci	*retlen = orig_len;
12462306a36Sopenharmony_ci
12562306a36Sopenharmony_ci	return orig_len;
12662306a36Sopenharmony_ci}
12762306a36Sopenharmony_ci
12862306a36Sopenharmony_cistatic int bcm47xxsflash_write_st(struct mtd_info *mtd, u32 offset, size_t len,
12962306a36Sopenharmony_ci				  const u_char *buf)
13062306a36Sopenharmony_ci{
13162306a36Sopenharmony_ci	struct bcm47xxsflash *b47s = mtd->priv;
13262306a36Sopenharmony_ci	int written = 0;
13362306a36Sopenharmony_ci
13462306a36Sopenharmony_ci	/* Enable writes */
13562306a36Sopenharmony_ci	bcm47xxsflash_cmd(b47s, OPCODE_ST_WREN);
13662306a36Sopenharmony_ci
13762306a36Sopenharmony_ci	/* Write first byte */
13862306a36Sopenharmony_ci	b47s->cc_write(b47s, BCMA_CC_FLASHADDR, offset);
13962306a36Sopenharmony_ci	b47s->cc_write(b47s, BCMA_CC_FLASHDATA, *buf++);
14062306a36Sopenharmony_ci
14162306a36Sopenharmony_ci	/* Program page */
14262306a36Sopenharmony_ci	if (b47s->bcma_cc->core->id.rev < 20) {
14362306a36Sopenharmony_ci		bcm47xxsflash_cmd(b47s, OPCODE_ST_PP);
14462306a36Sopenharmony_ci		return 1; /* 1B written */
14562306a36Sopenharmony_ci	}
14662306a36Sopenharmony_ci
14762306a36Sopenharmony_ci	/* Program page and set CSA (on newer chips we can continue writing) */
14862306a36Sopenharmony_ci	bcm47xxsflash_cmd(b47s, OPCODE_ST_CSA | OPCODE_ST_PP);
14962306a36Sopenharmony_ci	offset++;
15062306a36Sopenharmony_ci	len--;
15162306a36Sopenharmony_ci	written++;
15262306a36Sopenharmony_ci
15362306a36Sopenharmony_ci	while (len > 0) {
15462306a36Sopenharmony_ci		/* Page boundary, another function call is needed */
15562306a36Sopenharmony_ci		if ((offset & 0xFF) == 0)
15662306a36Sopenharmony_ci			break;
15762306a36Sopenharmony_ci
15862306a36Sopenharmony_ci		bcm47xxsflash_cmd(b47s, OPCODE_ST_CSA | *buf++);
15962306a36Sopenharmony_ci		offset++;
16062306a36Sopenharmony_ci		len--;
16162306a36Sopenharmony_ci		written++;
16262306a36Sopenharmony_ci	}
16362306a36Sopenharmony_ci
16462306a36Sopenharmony_ci	/* All done, drop CSA & poll */
16562306a36Sopenharmony_ci	b47s->cc_write(b47s, BCMA_CC_FLASHCTL, 0);
16662306a36Sopenharmony_ci	udelay(1);
16762306a36Sopenharmony_ci	if (bcm47xxsflash_poll(b47s, HZ / 10))
16862306a36Sopenharmony_ci		pr_err("Flash rejected dropping CSA\n");
16962306a36Sopenharmony_ci
17062306a36Sopenharmony_ci	return written;
17162306a36Sopenharmony_ci}
17262306a36Sopenharmony_ci
17362306a36Sopenharmony_cistatic int bcm47xxsflash_write_at(struct mtd_info *mtd, u32 offset, size_t len,
17462306a36Sopenharmony_ci				  const u_char *buf)
17562306a36Sopenharmony_ci{
17662306a36Sopenharmony_ci	struct bcm47xxsflash *b47s = mtd->priv;
17762306a36Sopenharmony_ci	u32 mask = b47s->blocksize - 1;
17862306a36Sopenharmony_ci	u32 page = (offset & ~mask) << 1;
17962306a36Sopenharmony_ci	u32 byte = offset & mask;
18062306a36Sopenharmony_ci	int written = 0;
18162306a36Sopenharmony_ci
18262306a36Sopenharmony_ci	/* If we don't overwrite whole page, read it to the buffer first */
18362306a36Sopenharmony_ci	if (byte || (len < b47s->blocksize)) {
18462306a36Sopenharmony_ci		int err;
18562306a36Sopenharmony_ci
18662306a36Sopenharmony_ci		b47s->cc_write(b47s, BCMA_CC_FLASHADDR, page);
18762306a36Sopenharmony_ci		bcm47xxsflash_cmd(b47s, OPCODE_AT_BUF1_LOAD);
18862306a36Sopenharmony_ci		/* 250 us for AT45DB321B */
18962306a36Sopenharmony_ci		err = bcm47xxsflash_poll(b47s, HZ / 1000);
19062306a36Sopenharmony_ci		if (err) {
19162306a36Sopenharmony_ci			pr_err("Timeout reading page 0x%X info buffer\n", page);
19262306a36Sopenharmony_ci			return err;
19362306a36Sopenharmony_ci		}
19462306a36Sopenharmony_ci	}
19562306a36Sopenharmony_ci
19662306a36Sopenharmony_ci	/* Change buffer content with our data */
19762306a36Sopenharmony_ci	while (len > 0) {
19862306a36Sopenharmony_ci		/* Page boundary, another function call is needed */
19962306a36Sopenharmony_ci		if (byte == b47s->blocksize)
20062306a36Sopenharmony_ci			break;
20162306a36Sopenharmony_ci
20262306a36Sopenharmony_ci		b47s->cc_write(b47s, BCMA_CC_FLASHADDR, byte++);
20362306a36Sopenharmony_ci		b47s->cc_write(b47s, BCMA_CC_FLASHDATA, *buf++);
20462306a36Sopenharmony_ci		bcm47xxsflash_cmd(b47s, OPCODE_AT_BUF1_WRITE);
20562306a36Sopenharmony_ci		len--;
20662306a36Sopenharmony_ci		written++;
20762306a36Sopenharmony_ci	}
20862306a36Sopenharmony_ci
20962306a36Sopenharmony_ci	/* Program page with the buffer content */
21062306a36Sopenharmony_ci	b47s->cc_write(b47s, BCMA_CC_FLASHADDR, page);
21162306a36Sopenharmony_ci	bcm47xxsflash_cmd(b47s, OPCODE_AT_BUF1_PROGRAM);
21262306a36Sopenharmony_ci
21362306a36Sopenharmony_ci	return written;
21462306a36Sopenharmony_ci}
21562306a36Sopenharmony_ci
21662306a36Sopenharmony_cistatic int bcm47xxsflash_write(struct mtd_info *mtd, loff_t to, size_t len,
21762306a36Sopenharmony_ci			       size_t *retlen, const u_char *buf)
21862306a36Sopenharmony_ci{
21962306a36Sopenharmony_ci	struct bcm47xxsflash *b47s = mtd->priv;
22062306a36Sopenharmony_ci	int written;
22162306a36Sopenharmony_ci
22262306a36Sopenharmony_ci	/* Writing functions can return without writing all passed data, for
22362306a36Sopenharmony_ci	 * example when the hardware is too old or when we git page boundary.
22462306a36Sopenharmony_ci	 */
22562306a36Sopenharmony_ci	while (len > 0) {
22662306a36Sopenharmony_ci		switch (b47s->type) {
22762306a36Sopenharmony_ci		case BCM47XXSFLASH_TYPE_ST:
22862306a36Sopenharmony_ci			written = bcm47xxsflash_write_st(mtd, to, len, buf);
22962306a36Sopenharmony_ci			break;
23062306a36Sopenharmony_ci		case BCM47XXSFLASH_TYPE_ATMEL:
23162306a36Sopenharmony_ci			written = bcm47xxsflash_write_at(mtd, to, len, buf);
23262306a36Sopenharmony_ci			break;
23362306a36Sopenharmony_ci		default:
23462306a36Sopenharmony_ci			BUG_ON(1);
23562306a36Sopenharmony_ci		}
23662306a36Sopenharmony_ci		if (written < 0) {
23762306a36Sopenharmony_ci			pr_err("Error writing at offset 0x%llX\n", to);
23862306a36Sopenharmony_ci			return written;
23962306a36Sopenharmony_ci		}
24062306a36Sopenharmony_ci		to += (loff_t)written;
24162306a36Sopenharmony_ci		len -= written;
24262306a36Sopenharmony_ci		*retlen += written;
24362306a36Sopenharmony_ci		buf += written;
24462306a36Sopenharmony_ci	}
24562306a36Sopenharmony_ci
24662306a36Sopenharmony_ci	return 0;
24762306a36Sopenharmony_ci}
24862306a36Sopenharmony_ci
24962306a36Sopenharmony_cistatic void bcm47xxsflash_fill_mtd(struct bcm47xxsflash *b47s,
25062306a36Sopenharmony_ci				   struct device *dev)
25162306a36Sopenharmony_ci{
25262306a36Sopenharmony_ci	struct mtd_info *mtd = &b47s->mtd;
25362306a36Sopenharmony_ci
25462306a36Sopenharmony_ci	mtd->priv = b47s;
25562306a36Sopenharmony_ci	mtd->dev.parent = dev;
25662306a36Sopenharmony_ci	mtd->name = "bcm47xxsflash";
25762306a36Sopenharmony_ci
25862306a36Sopenharmony_ci	mtd->type = MTD_NORFLASH;
25962306a36Sopenharmony_ci	mtd->flags = MTD_CAP_NORFLASH;
26062306a36Sopenharmony_ci	mtd->size = b47s->size;
26162306a36Sopenharmony_ci	mtd->erasesize = b47s->blocksize;
26262306a36Sopenharmony_ci	mtd->writesize = 1;
26362306a36Sopenharmony_ci	mtd->writebufsize = 1;
26462306a36Sopenharmony_ci
26562306a36Sopenharmony_ci	mtd->_erase = bcm47xxsflash_erase;
26662306a36Sopenharmony_ci	mtd->_read = bcm47xxsflash_read;
26762306a36Sopenharmony_ci	mtd->_write = bcm47xxsflash_write;
26862306a36Sopenharmony_ci}
26962306a36Sopenharmony_ci
27062306a36Sopenharmony_ci/**************************************************
27162306a36Sopenharmony_ci * BCMA
27262306a36Sopenharmony_ci **************************************************/
27362306a36Sopenharmony_ci
27462306a36Sopenharmony_cistatic int bcm47xxsflash_bcma_cc_read(struct bcm47xxsflash *b47s, u16 offset)
27562306a36Sopenharmony_ci{
27662306a36Sopenharmony_ci	return bcma_cc_read32(b47s->bcma_cc, offset);
27762306a36Sopenharmony_ci}
27862306a36Sopenharmony_ci
27962306a36Sopenharmony_cistatic void bcm47xxsflash_bcma_cc_write(struct bcm47xxsflash *b47s, u16 offset,
28062306a36Sopenharmony_ci					u32 value)
28162306a36Sopenharmony_ci{
28262306a36Sopenharmony_ci	bcma_cc_write32(b47s->bcma_cc, offset, value);
28362306a36Sopenharmony_ci}
28462306a36Sopenharmony_ci
28562306a36Sopenharmony_cistatic int bcm47xxsflash_bcma_probe(struct platform_device *pdev)
28662306a36Sopenharmony_ci{
28762306a36Sopenharmony_ci	struct device *dev = &pdev->dev;
28862306a36Sopenharmony_ci	struct bcma_sflash *sflash = dev_get_platdata(dev);
28962306a36Sopenharmony_ci	struct bcm47xxsflash *b47s;
29062306a36Sopenharmony_ci	struct resource *res;
29162306a36Sopenharmony_ci	int err;
29262306a36Sopenharmony_ci
29362306a36Sopenharmony_ci	b47s = devm_kzalloc(dev, sizeof(*b47s), GFP_KERNEL);
29462306a36Sopenharmony_ci	if (!b47s)
29562306a36Sopenharmony_ci		return -ENOMEM;
29662306a36Sopenharmony_ci
29762306a36Sopenharmony_ci	res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
29862306a36Sopenharmony_ci	if (!res) {
29962306a36Sopenharmony_ci		dev_err(dev, "invalid resource\n");
30062306a36Sopenharmony_ci		return -EINVAL;
30162306a36Sopenharmony_ci	}
30262306a36Sopenharmony_ci	if (!devm_request_mem_region(dev, res->start, resource_size(res),
30362306a36Sopenharmony_ci				     res->name)) {
30462306a36Sopenharmony_ci		dev_err(dev, "can't request region for resource %pR\n", res);
30562306a36Sopenharmony_ci		return -EBUSY;
30662306a36Sopenharmony_ci	}
30762306a36Sopenharmony_ci
30862306a36Sopenharmony_ci	b47s->bcma_cc = container_of(sflash, struct bcma_drv_cc, sflash);
30962306a36Sopenharmony_ci	b47s->cc_read = bcm47xxsflash_bcma_cc_read;
31062306a36Sopenharmony_ci	b47s->cc_write = bcm47xxsflash_bcma_cc_write;
31162306a36Sopenharmony_ci
31262306a36Sopenharmony_ci	/*
31362306a36Sopenharmony_ci	 * On old MIPS devices cache was magically invalidated when needed,
31462306a36Sopenharmony_ci	 * allowing us to use cached access and gain some performance. Trying
31562306a36Sopenharmony_ci	 * the same on ARM based BCM53573 results in flash corruptions, we need
31662306a36Sopenharmony_ci	 * to use uncached access for it.
31762306a36Sopenharmony_ci	 *
31862306a36Sopenharmony_ci	 * It may be arch specific, but right now there is only 1 ARM SoC using
31962306a36Sopenharmony_ci	 * this driver, so let's follow Broadcom's reference code and check
32062306a36Sopenharmony_ci	 * ChipCommon revision.
32162306a36Sopenharmony_ci	 */
32262306a36Sopenharmony_ci	if (b47s->bcma_cc->core->id.rev == 54)
32362306a36Sopenharmony_ci		b47s->window = ioremap(res->start, resource_size(res));
32462306a36Sopenharmony_ci	else
32562306a36Sopenharmony_ci		b47s->window = ioremap_cache(res->start, resource_size(res));
32662306a36Sopenharmony_ci	if (!b47s->window) {
32762306a36Sopenharmony_ci		dev_err(dev, "ioremap failed for resource %pR\n", res);
32862306a36Sopenharmony_ci		return -ENOMEM;
32962306a36Sopenharmony_ci	}
33062306a36Sopenharmony_ci
33162306a36Sopenharmony_ci	switch (b47s->bcma_cc->capabilities & BCMA_CC_CAP_FLASHT) {
33262306a36Sopenharmony_ci	case BCMA_CC_FLASHT_STSER:
33362306a36Sopenharmony_ci		b47s->type = BCM47XXSFLASH_TYPE_ST;
33462306a36Sopenharmony_ci		break;
33562306a36Sopenharmony_ci	case BCMA_CC_FLASHT_ATSER:
33662306a36Sopenharmony_ci		b47s->type = BCM47XXSFLASH_TYPE_ATMEL;
33762306a36Sopenharmony_ci		break;
33862306a36Sopenharmony_ci	}
33962306a36Sopenharmony_ci
34062306a36Sopenharmony_ci	b47s->blocksize = sflash->blocksize;
34162306a36Sopenharmony_ci	b47s->numblocks = sflash->numblocks;
34262306a36Sopenharmony_ci	b47s->size = sflash->size;
34362306a36Sopenharmony_ci	bcm47xxsflash_fill_mtd(b47s, &pdev->dev);
34462306a36Sopenharmony_ci
34562306a36Sopenharmony_ci	platform_set_drvdata(pdev, b47s);
34662306a36Sopenharmony_ci
34762306a36Sopenharmony_ci	err = mtd_device_parse_register(&b47s->mtd, probes, NULL, NULL, 0);
34862306a36Sopenharmony_ci	if (err) {
34962306a36Sopenharmony_ci		pr_err("Failed to register MTD device: %d\n", err);
35062306a36Sopenharmony_ci		iounmap(b47s->window);
35162306a36Sopenharmony_ci		return err;
35262306a36Sopenharmony_ci	}
35362306a36Sopenharmony_ci
35462306a36Sopenharmony_ci	if (bcm47xxsflash_poll(b47s, HZ / 10))
35562306a36Sopenharmony_ci		pr_warn("Serial flash busy\n");
35662306a36Sopenharmony_ci
35762306a36Sopenharmony_ci	return 0;
35862306a36Sopenharmony_ci}
35962306a36Sopenharmony_ci
36062306a36Sopenharmony_cistatic int bcm47xxsflash_bcma_remove(struct platform_device *pdev)
36162306a36Sopenharmony_ci{
36262306a36Sopenharmony_ci	struct bcm47xxsflash *b47s = platform_get_drvdata(pdev);
36362306a36Sopenharmony_ci
36462306a36Sopenharmony_ci	mtd_device_unregister(&b47s->mtd);
36562306a36Sopenharmony_ci	iounmap(b47s->window);
36662306a36Sopenharmony_ci
36762306a36Sopenharmony_ci	return 0;
36862306a36Sopenharmony_ci}
36962306a36Sopenharmony_ci
37062306a36Sopenharmony_cistatic struct platform_driver bcma_sflash_driver = {
37162306a36Sopenharmony_ci	.probe	= bcm47xxsflash_bcma_probe,
37262306a36Sopenharmony_ci	.remove = bcm47xxsflash_bcma_remove,
37362306a36Sopenharmony_ci	.driver = {
37462306a36Sopenharmony_ci		.name = "bcma_sflash",
37562306a36Sopenharmony_ci	},
37662306a36Sopenharmony_ci};
37762306a36Sopenharmony_ci
37862306a36Sopenharmony_ci/**************************************************
37962306a36Sopenharmony_ci * Init
38062306a36Sopenharmony_ci **************************************************/
38162306a36Sopenharmony_ci
38262306a36Sopenharmony_cimodule_platform_driver(bcma_sflash_driver);
383