WhyCan Forum

本站无需注册,无需积分,无需回复可下载所有资料,如果真的喜欢小站,请您注册之后请至少回复一个帖子激活Id,谢谢支持! 站长QQ: 516333132 (挖坑网/填坑网) admin@whycan.cn

您尚未登录。

#1 2019-05-10 15:08:37

晕哥
Administrator
注册时间: 2017-09-06
累计积分: 6,515

Linux 平台 华邦(Winbond) spi flash OPT & UID 驱动

https://github.com/CreatorDev/openwrt/blob/master-pistachio/target/linux/pistachio/patches-4.1/0127-mtd-spi-nor-add-support-for-winbond-OTP.patch

From 84a933323d59b27ae85cf16deddc254a0b17e450 Mon Sep 17 00:00:00 2001
From: Shraddha Chaudhari <Shraddha.Chaudhari@imgtec.com>
Date: Tue, 5 Jan 2016 11:16:43 +0530
Subject: mtd: spi-nor: add support for winbond OTP

Few winbond spi nor devices support 3 x 256 bytes
of security registers, add support for read, write, lock
of these registers as user OTP.

Also add support for reading winbond devices unique ID
as factory OTP.

Change-Id: I8bb4d360c5f12b6b927d1135927cef809dee8d51
Signed-off-by: Shraddha Chaudhari <Shraddha.Chaudhari@imgtec.com>
---
 drivers/mtd/spi-nor/Kconfig          |   8 +
 drivers/mtd/spi-nor/Makefile         |   1 +
 drivers/mtd/spi-nor/spi-nor-common.h |   8 +
 drivers/mtd/spi-nor/spi-nor.c        |  14 +-
 drivers/mtd/spi-nor/winbond-otp.c    | 482 +++++++++++++++++++++++++++++++++++
 drivers/mtd/spi-nor/winbond-otp.h    |  20 ++
 6 files changed, 529 insertions(+), 4 deletions(-)
 create mode 100644 drivers/mtd/spi-nor/spi-nor-common.h
 create mode 100644 drivers/mtd/spi-nor/winbond-otp.c
 create mode 100644 drivers/mtd/spi-nor/winbond-otp.h

diff --git a/drivers/mtd/spi-nor/Kconfig b/drivers/mtd/spi-nor/Kconfig
index 64a4f0e..e157c33 100644
--- a/drivers/mtd/spi-nor/Kconfig
+++ b/drivers/mtd/spi-nor/Kconfig
@@ -28,4 +28,12 @@ config SPI_FSL_QUADSPI
 	  This enables support for the Quad SPI controller in master mode.
 	  We only connect the NOR to this controller now.
 
+config MTD_SPI_NOR_WINBOND_OTP
+	bool "Support for winbond security register and unique ID"
+	depends on MTD_SPI_NOR
+	default n
+	help
+	  This enables support for read/write of winbond security registers
+	  as user OTP and also reading of NOR unique ID as factory OTP.
+
 endif # MTD_SPI_NOR
diff --git a/drivers/mtd/spi-nor/Makefile b/drivers/mtd/spi-nor/Makefile
index 6a7ce14..6aba941 100644
--- a/drivers/mtd/spi-nor/Makefile
+++ b/drivers/mtd/spi-nor/Makefile
@@ -1,2 +1,3 @@
 obj-$(CONFIG_MTD_SPI_NOR)	+= spi-nor.o
+obj-$(CONFIG_MTD_SPI_NOR_WINBOND_OTP)	+= winbond-otp.o
 obj-$(CONFIG_SPI_FSL_QUADSPI)	+= fsl-quadspi.o
diff --git a/drivers/mtd/spi-nor/spi-nor-common.h b/drivers/mtd/spi-nor/spi-nor-common.h
new file mode 100644
index 0000000..ff15d42
--- /dev/null
+++ b/drivers/mtd/spi-nor/spi-nor-common.h
@@ -0,0 +1,8 @@
+#ifndef SPI_NOR_COMMON_H
+#define SPI_NOR_COMMON_H
+
+int spi_nor_wait_till_ready(struct spi_nor *nor);
+int spi_nor_lock_and_prep(struct spi_nor *nor, enum spi_nor_ops ops);
+void spi_nor_unlock_and_unprep(struct spi_nor *nor, enum spi_nor_ops ops);
+
+#endif
diff --git a/drivers/mtd/spi-nor/spi-nor.c b/drivers/mtd/spi-nor/spi-nor.c
index 1194f33..46d2f06 100644
--- a/drivers/mtd/spi-nor/spi-nor.c
+++ b/drivers/mtd/spi-nor/spi-nor.c
@@ -22,6 +22,8 @@
 #include <linux/of_platform.h>
 #include <linux/spi/flash.h>
 #include <linux/mtd/spi-nor.h>
+#include "winbond-otp.h"
+#include "spi-nor-common.h"
 
 /* Define max times to check status register before we give up. */
 #define	MAX_READY_WAIT_JIFFIES	(40 * HZ) /* M25P16 specs 40s max chip erase */
@@ -55,6 +57,7 @@ struct flash_info {
 #define	SPI_NOR_DUAL_READ	0x20    /* Flash supports Dual Read */
 #define	SPI_NOR_QUAD_READ	0x40    /* Flash supports Quad Read */
 #define	USE_FSR			0x80	/* use flag status register */
+#define WINBOND_OTP		0x100	/* use winbond security reg as OTP */
 };
 
 #define JEDEC_MFR(info)	((info)->id[0])
@@ -231,7 +234,7 @@ static int spi_nor_ready(struct spi_nor *nor)
  * Service routine to read status register until ready, or timeout occurs.
  * Returns non-zero if error.
  */
-static int spi_nor_wait_till_ready(struct spi_nor *nor)
+int spi_nor_wait_till_ready(struct spi_nor *nor)
 {
 	unsigned long deadline;
 	int timeout = 0, ret;
@@ -268,7 +271,7 @@ static int erase_chip(struct spi_nor *nor)
 	return nor->write_reg(nor, SPINOR_OP_CHIP_ERASE, NULL, 0, 0);
 }
 
-static int spi_nor_lock_and_prep(struct spi_nor *nor, enum spi_nor_ops ops)
+int spi_nor_lock_and_prep(struct spi_nor *nor, enum spi_nor_ops ops)
 {
 	int ret = 0;
 
@@ -285,7 +288,7 @@ static int spi_nor_lock_and_prep(struct spi_nor *nor, enum spi_nor_ops ops)
 	return ret;
 }
 
-static void spi_nor_unlock_and_unprep(struct spi_nor *nor, enum spi_nor_ops ops)
+void spi_nor_unlock_and_unprep(struct spi_nor *nor, enum spi_nor_ops ops)
 {
 	if (nor->unprepare)
 		nor->unprepare(nor, ops);
@@ -611,7 +614,7 @@ static const struct spi_device_id spi_nor_ids[] = {
 	{ "s25sl032a",  INFO(0x010215,      0,  64 * 1024,  64, 0) },
 	{ "s25sl064a",  INFO(0x010216,      0,  64 * 1024, 128, 0) },
 	{ "s25fl008k",  INFO(0xef4014,      0,  64 * 1024,  16, SECT_4K) },
-	{ "s25fl016k",  INFO(0xef4015,      0,  64 * 1024,  32, SECT_4K) },
+	{ "s25fl016k",  INFO(0xef4015,      0,  64 * 1024,  32, SECT_4K | WINBOND_OTP) },
 	{ "s25fl064k",  INFO(0xef4017,      0,  64 * 1024, 128, SECT_4K) },
 	{ "s25fl132k",  INFO(0x014016,      0,  64 * 1024,  64, 0) },
 
@@ -1078,6 +1081,9 @@ int spi_nor_scan(struct spi_nor *nor, const char *name, enum read_mode mode)
 		mtd->_unlock = spi_nor_unlock;
 	}
 
+	if (info->flags & WINBOND_OTP)
+		winbond_otp_register(mtd);
+
 	/* sst nor chips use AAI word program */
 	if (info->flags & SST_WRITE)
 		mtd->_write = sst_write;
diff --git a/drivers/mtd/spi-nor/winbond-otp.c b/drivers/mtd/spi-nor/winbond-otp.c
new file mode 100644
index 0000000..a9db770
--- /dev/null
+++ b/drivers/mtd/spi-nor/winbond-otp.c
@@ -0,0 +1,482 @@
+/*
+ * Imagination Technologies
+ *
+ * Copyright (c) 2015 Imagination Technologies Ltd.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 as published by
+ * the Free Software Foundation.
+ *
+ * This driver provides read/write access to the 3 x 256 bytes security
+ * registers as user OTP and unique ID of the NOR can be read as factory OTP
+ *
+ */
+
+#include <linux/types.h>
+#include <linux/mtd/mtd.h>
+#include <linux/mtd/spi-nor.h>
+#include <linux/mutex.h>
+#include <linux/sched.h>
+#include <linux/slab.h>
+#include "spi-nor-common.h"
+
+#define SECURITY_REG_START_ADDR		0x1000 /*first security register addr*/
+#define SECURITY_REG_ADDR_OFFSET	0x1000 /*diff between consecutive reg*/
+#define SECURITY_REG_NUM		3 /* number of security registers */
+#define SECURITY_REG_SIZE		256 /* bytes per security register */
+#define SECURITY_REG_TOTAL_SIZE		(SECURITY_REG_NUM * SECURITY_REG_SIZE)
+#define SPI_NOR_UNIQUE_ID_LEN		8 /*number of bytes of unique ID */
+
+/* SPI FLASH opcodes */
+#define SPINOR_OP_RD_SR2		0x35 /* Read status register 2 */
+#define SPINOR_OP_PR_SECURITY_REG	0x42 /* Program security register */
+#define SPINOR_OP_ER_SECURITY_REG	0x44 /* Erase security register */
+#define SPINOR_OP_RD_SECURITY_REG	0x48 /* Read security register */
+#define SPINOR_OP_RD_UNIQUE_ID		0x4B /* Read unique id */
+
+/* Status register 2 */
+#define SR2_LB1_BIT			3 /* security register lock bit 1 */
+
+/* Get start addr of the security reg*/
+#define SEC_REG_START_ADDR(addr) (addr & 0x3000)
+
+static inline struct spi_nor *mtd_to_spi_nor(struct mtd_info *mtd)
+{
+	return mtd->priv;
+}
+
+static inline int write_enable(struct spi_nor *nor)
+{
+	return nor->write_reg(nor, SPINOR_OP_WREN, NULL, 0, 0);
+}
+
+static inline int write_disable(struct spi_nor *nor)
+{
+	return nor->write_reg(nor, SPINOR_OP_WRDI, NULL, 0, 0);
+}
+
+static int read_sr(struct spi_nor *nor, u8 opcode, u8 *val)
+{
+	int ret;
+
+	ret = nor->read_reg(nor, opcode, val, 1);
+	if (ret < 0)
+		pr_err("error %d reading SR\n", ret);
+	return ret;
+}
+
+/*
+ * Converts address range
+ *	0 - 0xFF	-> 0x1000 - 0x10FF
+ *	0x100 - 0x1FF	-> 0x2000 - 0x20FF
+ *	0x200 - 0x2FF	-> 0x3000 - 0x30FF
+ *
+ * This func assumes that sanity checks on addr are done and is in valid range
+ */
+static loff_t translate_addr(loff_t addr)
+{
+	int i;
+	loff_t new_addr = SECURITY_REG_START_ADDR;
+
+	for (i = 0; i < SECURITY_REG_NUM; i++) {
+		if (addr < ((i+1)*SECURITY_REG_SIZE)) {
+			new_addr |= addr & (SECURITY_REG_SIZE-1);
+			break;
+		}
+		new_addr += SECURITY_REG_ADDR_OFFSET;
+	}
+
+	return new_addr;
+}
+
+/*
+ * Return 3 blocks of 256 bytes security register as user OTP,
+ * address of these blocks will be 0, 0x100, 0x200
+ * driver will convert these address to actual address while doing
+ * read/write
+ */
+static int winbond_get_user_otp_info(struct mtd_info *mtd, size_t len,
+					size_t *retlen,
+					struct otp_info *otpinfo)
+{
+	u8 val;
+	int i, ret;
+	struct spi_nor *nor = mtd_to_spi_nor(mtd);
+
+	mutex_lock(&nor->lock);
+	ret = read_sr(nor, SPINOR_OP_RD_SR2, &val);
+	mutex_unlock(&nor->lock);
+
+	if (ret < 0)
+		return ret;
+
+	for (i = 0; i < SECURITY_REG_NUM; i++) {
+		otpinfo[i].start = i * SECURITY_REG_SIZE;
+		otpinfo[i].length = SECURITY_REG_SIZE;
+		otpinfo[i].locked = !!(val & BIT(SR2_LB1_BIT + i));
+	}
+
+	*retlen = SECURITY_REG_NUM * sizeof(*otpinfo);
+
+	return 0;
+}
+
+static int spi_otp_read(struct spi_nor *nor, loff_t from,
+					size_t len, size_t *retlen, u_char *buf)
+{
+	struct spi_nor_xfer_cfg cfg = {
+		.cmd = SPINOR_OP_RD_SECURITY_REG,
+		.addr = from,
+		.addr_width = nor->addr_width,
+		.mode = SPI_NOR_NORMAL,
+		.dummy_cycles = 8,
+	};
+
+	return nor->read_xfer(nor, &cfg, buf, len, retlen);
+}
+
+static int spi_otp_write(struct spi_nor *nor, loff_t to,
+					size_t len, size_t *retlen, u_char *buf)
+{
+	struct spi_nor_xfer_cfg cfg = {
+		.cmd = SPINOR_OP_PR_SECURITY_REG,
+		.addr = to,
+		.addr_width = nor->addr_width,
+		.mode = SPI_NOR_NORMAL,
+	};
+
+	return nor->write_xfer(nor, &cfg, buf, len, retlen);
+}
+
+static int spi_otp_erase(struct spi_nor *nor, loff_t offs)
+{
+	size_t temp_retlen;
+	struct spi_nor_xfer_cfg cfg = {
+		.cmd = SPINOR_OP_ER_SECURITY_REG,
+		.addr = offs,
+		.addr_width = nor->addr_width,
+		.mode = SPI_NOR_NORMAL,
+	};
+
+	return nor->write_xfer(nor, &cfg, NULL, 0, &temp_retlen);
+}
+
+static int spi_read_uniqueid(struct spi_nor *nor, u8 *buf)
+{
+	size_t temp_retlen;
+	struct spi_nor_xfer_cfg cfg = {
+		.cmd = SPINOR_OP_RD_UNIQUE_ID,
+		.addr_width = 0,
+		.mode = SPI_NOR_NORMAL,
+		.dummy_cycles = 32,
+	};
+
+	return nor->read_xfer(nor, &cfg, buf, SPI_NOR_UNIQUE_ID_LEN,
+				&temp_retlen);
+}
+
+
+static int winbond_read_user_otp(struct mtd_info *mtd, loff_t from,
+					size_t len, size_t *retlen, u_char *buf)
+{
+	int ret;
+	u32 i, read_len, end_addr, sreg_offset;
+	loff_t temp_addr;
+	struct spi_nor *nor = mtd_to_spi_nor(mtd);
+
+	*retlen = 0;
+
+	if (from < 0 || from >= SECURITY_REG_TOTAL_SIZE
+		     || (from + len) > SECURITY_REG_TOTAL_SIZE)
+		return -EINVAL;
+
+	if (!len)
+		return 0;
+
+	end_addr = from + len;
+
+	ret = spi_nor_lock_and_prep(nor, SPI_NOR_OPS_READ);
+	if (ret)
+		return ret;
+
+	for (i = from; i < end_addr; i += read_len) {
+		sreg_offset = i & (SECURITY_REG_SIZE-1);
+		/* if offset not on boundary, read first few bytes */
+		if (sreg_offset) {
+			/* check if everything has to be read from 1 reg */
+			if ((sreg_offset + len) <= SECURITY_REG_SIZE)
+				read_len = len;
+			else
+				read_len = SECURITY_REG_SIZE - sreg_offset;
+		}
+		/* if it is last chunk, read the remaining bytes */
+		else if ((end_addr - i) < SECURITY_REG_SIZE)
+			read_len = end_addr - i;
+		else
+			read_len = SECURITY_REG_SIZE;
+
+		temp_addr = translate_addr(i);
+		ret = spi_otp_read(nor, temp_addr, read_len, retlen,
+					buf + (i-from));
+		if (ret < 0)
+			goto error;
+	}
+error:
+	spi_nor_unlock_and_unprep(nor, SPI_NOR_OPS_READ);
+	return ret;
+}
+
+/*
+ * This func assumes that offset is within valid range of security registers,
+ * valid offset are 0x1000, 0x2000 or 0x3000
+ */
+static int winbond_erase_security_reg(struct spi_nor *nor, loff_t offset)
+{
+	int ret;
+
+	ret = write_enable(nor);
+	if (ret < 0)
+		return ret;
+
+	ret = spi_nor_wait_till_ready(nor);
+	if (ret)
+		return ret;
+
+	ret = spi_otp_erase(nor, offset);
+	if (ret < 0)
+		return ret;
+
+	ret = spi_nor_wait_till_ready(nor);
+
+	return ret;
+}
+
+/*
+ * This function does read, modify locally, erase and write to the register to
+ * be written
+ * It doesn't do any range checks on reg_addr, sreg_offset, len
+ */
+static int winbond_write_security_reg(struct spi_nor *nor, loff_t reg_addr,
+					u32 sreg_offset, size_t len,
+					size_t *retlen, u_char *buf)
+{
+	int ret;
+	size_t temp_retlen = 0;
+	u8 *reg_buffer;
+
+	if (unlikely(sreg_offset + len > SECURITY_REG_SIZE))
+		return -EINVAL;
+
+	reg_buffer = kmalloc(SECURITY_REG_SIZE, GFP_KERNEL);
+	if (!reg_buffer)
+		return -ENOMEM;
+
+	/* read the security register */
+	ret = spi_otp_read(nor, reg_addr, SECURITY_REG_SIZE, &temp_retlen,
+				reg_buffer);
+	if (ret < 0 || temp_retlen != SECURITY_REG_SIZE)
+		goto error;
+
+	/* modify the part to be written */
+	memcpy(reg_buffer + sreg_offset, buf, len);
+
+	/* erase the security register */
+	ret = winbond_erase_security_reg(nor, reg_addr);
+	if (ret < 0)
+		goto error;
+
+	/* write the security reg*/
+	ret = write_enable(nor);
+	if (ret < 0)
+		goto error;
+
+	ret = spi_nor_wait_till_ready(nor);
+	if (ret)
+		goto error;
+
+	temp_retlen = 0;
+
+	ret = spi_otp_write(nor, reg_addr, SECURITY_REG_SIZE, &temp_retlen,
+				reg_buffer);
+	if (ret < 0 || temp_retlen != SECURITY_REG_SIZE)
+		goto error;
+
+	ret = spi_nor_wait_till_ready(nor);
+
+	*retlen += len;
+
+error:
+	kfree(reg_buffer);
+	return ret;
+}
+
+static int winbond_write_user_otp(struct mtd_info *mtd, loff_t to,
+					size_t len, size_t *retlen, u_char *buf)
+{
+	int ret;
+	u32 i, write_len, end_addr, sreg_offset;
+	loff_t temp_addr;
+	struct spi_nor *nor = mtd_to_spi_nor(mtd);
+
+	*retlen = 0;
+
+	if (to < 0 || to >= SECURITY_REG_TOTAL_SIZE
+		   || (to + len) > SECURITY_REG_TOTAL_SIZE)
+		return -EINVAL;
+
+	if (!len)
+		return 0;
+
+	end_addr = to + len;
+
+	ret = spi_nor_lock_and_prep(nor, SPI_NOR_OPS_WRITE);
+	if (ret)
+		return ret;
+
+	for (i = to; i < end_addr; i += write_len) {
+		sreg_offset = i & (SECURITY_REG_SIZE-1);
+		/* if offset not on boundary, write first few bytes */
+		if (sreg_offset) {
+			/* check if everything has to be written in 1 reg */
+			if ((sreg_offset + len) <= SECURITY_REG_SIZE)
+				write_len = len;
+			else
+				write_len = SECURITY_REG_SIZE - sreg_offset;
+		}
+		/* if it is last chunk, write the remaining bytes */
+		else if ((end_addr - i) < SECURITY_REG_SIZE)
+			write_len = end_addr - i;
+		else
+			write_len = SECURITY_REG_SIZE;
+
+		temp_addr = translate_addr(i);
+		ret = winbond_write_security_reg(nor,
+					SEC_REG_START_ADDR(temp_addr),
+					sreg_offset, write_len,
+					retlen,	buf + (i-to));
+		if (ret < 0)
+			goto error;
+	}
+
+error:
+	spi_nor_unlock_and_unprep(nor, SPI_NOR_OPS_WRITE);
+	return ret;
+}
+
+static int winbond_lock_user_otp(struct mtd_info *mtd, loff_t from, size_t len)
+{
+	int ret;
+	u8 sr1, sr2, security_reg_num;
+	struct spi_nor *nor = mtd_to_spi_nor(mtd);
+
+	/* allow locking 1 register at a time,
+	 * so ensure that len is 256
+	 * also check if address is on security register boundary
+	 */
+	if (len != SECURITY_REG_SIZE || from < 0
+		|| from >= SECURITY_REG_TOTAL_SIZE
+		|| from & (SECURITY_REG_SIZE - 1))
+		return -EINVAL;
+
+	/* find out the security reg to set */
+	security_reg_num = from / SECURITY_REG_SIZE;
+
+	if (unlikely(security_reg_num > (SECURITY_REG_NUM-1)))
+		return -EINVAL;
+
+	ret = spi_nor_lock_and_prep(nor, SPI_NOR_OPS_LOCK);
+	if (ret)
+		return ret;
+
+	/* read status registers */
+	ret = read_sr(nor, SPINOR_OP_RDSR, &sr1);
+	if (ret < 0)
+		goto error;
+
+	ret = read_sr(nor, SPINOR_OP_RD_SR2, &sr2);
+	if (ret < 0)
+		goto error;
+
+	ret = write_enable(nor);
+	if (ret < 0)
+		goto error;
+
+	/* set the corresponding LB bit in security register 2 */
+	sr2 |= BIT(SR2_LB1_BIT + security_reg_num);
+
+	/* write status registers */
+	nor->cmd_buf[0] = sr1;
+	nor->cmd_buf[1] = sr2;
+	ret = nor->write_reg(nor, SPINOR_OP_WRSR, nor->cmd_buf, 2, 0);
+
+	write_disable(nor);
+
+error:
+	spi_nor_unlock_and_unprep(nor, SPI_NOR_OPS_LOCK);
+	return ret;
+}
+
+/*
+ * Unique ID of NOR device will be reported as factory OTP
+ */
+static int winbond_get_fact_otp_info(struct mtd_info *mtd, size_t len,
+					size_t *retlen,
+					struct otp_info *otpinfo)
+{
+	otpinfo->start = 0;
+	otpinfo->length = SPI_NOR_UNIQUE_ID_LEN;
+	otpinfo->locked = 1;
+
+	*retlen = sizeof(*otpinfo);
+
+	return 0;
+}
+
+static int winbond_read_fact_otp(struct mtd_info *mtd, loff_t from, size_t len,
+					size_t *retlen, u_char *buf)
+{
+	int ret;
+
+	char unique_id[SPI_NOR_UNIQUE_ID_LEN] = {0};
+	struct spi_nor *nor = mtd_to_spi_nor(mtd);
+
+	*retlen = 0;
+
+	if (from < 0 || from >= SPI_NOR_UNIQUE_ID_LEN
+		     || (from + len) > SPI_NOR_UNIQUE_ID_LEN)
+		return -EINVAL;
+
+	if (!len)
+		return 0;
+
+	ret = spi_nor_lock_and_prep(nor, SPI_NOR_OPS_READ);
+	if (ret)
+		return ret;
+
+	ret = spi_read_uniqueid(nor, unique_id);
+	if (ret < 0)
+		goto error;
+
+	/* Read complete unique ID,but just copy whatever is requested */
+	memcpy(buf, unique_id + from, len);
+	*retlen = len;
+error:
+	spi_nor_unlock_and_unprep(nor, SPI_NOR_OPS_READ);
+	return ret;
+}
+
+void winbond_otp_register(struct mtd_info *mtd)
+{
+	struct spi_nor *nor = mtd_to_spi_nor(mtd);
+
+	if (nor->read_xfer && nor->write_xfer) {
+		mtd->_get_user_prot_info = winbond_get_user_otp_info;
+		mtd->_read_user_prot_reg = winbond_read_user_otp;
+		mtd->_write_user_prot_reg = winbond_write_user_otp;
+		mtd->_lock_user_prot_reg = winbond_lock_user_otp;
+		mtd->_get_fact_prot_info = winbond_get_fact_otp_info;
+		mtd->_read_fact_prot_reg = winbond_read_fact_otp;
+	} else
+		dev_err(nor->dev, "Required nor interfaces "
+				"(read_xfer, write_xfer) not defined\n");
+}
diff --git a/drivers/mtd/spi-nor/winbond-otp.h b/drivers/mtd/spi-nor/winbond-otp.h
new file mode 100644
index 0000000..29cbbcc
--- /dev/null
+++ b/drivers/mtd/spi-nor/winbond-otp.h
@@ -0,0 +1,20 @@
+/*
+ * Imagination Technologies
+ *
+ * Copyright (c) 2015 Imagination Technologies Ltd.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 as published by
+ * the Free Software Foundation.
+ */
+
+#ifndef WINBOND_OTP_H
+#define WINBOND_OTP_H
+
+#ifdef CONFIG_MTD_SPI_NOR_WINBOND_OTP
+void winbond_otp_register(struct mtd_info *mtd);
+#else
+static inline void winbond_otp_register(struct mtd_info *mtd) { return; }
+#endif
+
+#endif
-- 
1.9.1

离线

页脚