[<prev] [next>] [<thread-prev] [thread-next>] [day] [month] [year] [list]
Message-Id: <1300882619-19152-3-git-send-email-jamie@jamieiles.com>
Date: Wed, 23 Mar 2011 12:16:59 +0000
From: Jamie Iles <jamie@...ieiles.com>
To: linux-kernel@...r.kernel.org
Cc: gregkh@...e.de, Jamie Iles <jamie@...ieiles.com>
Subject: [RFC PATCH 2/2] picoxcellotp: add support for PC3X3 OTP devices
This adds support for the OTP in the PC3X3 family of devices. These
devices have 16KB of OTP memory and support a number of different
redundancy formats each of which increases the level of redundancy at
the cost of extra bit-storage.
Signed-off-by: Jamie Iles <jamie@...ieiles.com>
---
drivers/char/Kconfig | 7 +
drivers/char/Makefile | 1 +
drivers/char/picoxcellotp_pc3x3.c | 1078 +++++++++++++++++++++++++++++++++++++
3 files changed, 1086 insertions(+), 0 deletions(-)
create mode 100644 drivers/char/picoxcellotp_pc3x3.c
diff --git a/drivers/char/Kconfig b/drivers/char/Kconfig
index 1f74b7b..13632fc 100644
--- a/drivers/char/Kconfig
+++ b/drivers/char/Kconfig
@@ -117,6 +117,13 @@ config PICOXCELL_OTP
picoXcell SoC devices. The OTP memory can be used for key/code
storage and offers different redundancy mechanisms.
+config PICOXCELL_OTP_PC3X3
+ tristate "Enable support for Picochip PC3X3 OTP"
+ depends on PICOXCELL_PC3X3
+ help
+ Say y or m here to allow support for the OTP found in PC3X3 devices.
+ If you say m then the module will be called picoxcellotp_pc3x3.
+
config PRINTER
tristate "Parallel printer support"
depends on PARPORT
diff --git a/drivers/char/Makefile b/drivers/char/Makefile
index 0c42906..d2dd710 100644
--- a/drivers/char/Makefile
+++ b/drivers/char/Makefile
@@ -63,3 +63,4 @@ obj-$(CONFIG_RAMOOPS) += ramoops.o
obj-$(CONFIG_JS_RTC) += js-rtc.o
js-rtc-y = rtc.o
obj-$(CONFIG_PICOXCELL_OTP) += picoxcellotp.o
+obj-$(CONFIG_PICOXCELL_OTP_PC3X3) += picoxcellotp_pc3x3.o
diff --git a/drivers/char/picoxcellotp_pc3x3.c b/drivers/char/picoxcellotp_pc3x3.c
new file mode 100644
index 0000000..da875a6
--- /dev/null
+++ b/drivers/char/picoxcellotp_pc3x3.c
@@ -0,0 +1,1078 @@
+/*
+ * Copyright 2010-2011 Picochip LTD, Jamie Iles
+ * http://www.picochip.com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version
+ * 2 of the License, or (at your option) any later version.
+ *
+ * This driver implements a picoxcellotp backend for reading and writing the
+ * OTP memory in Picochip PC3X3 devices. This OTP can be used for executing
+ * secure boot code or for the secure storage of keys and any other user data.
+ */
+#define pr_fmt(fmt) "pc3x3otp: " fmt
+
+#include <linux/bitops.h>
+#include <linux/clk.h>
+#include <linux/delay.h>
+#include <linux/err.h>
+#include <linux/io.h>
+#include <linux/module.h>
+#include <linux/platform_device.h>
+
+#include "picoxcellotp.h"
+
+/*
+ * To test the user interface and most of the driver logic, we have a test
+ * mode whereby rather than writing to OTP we have a RAM buffer that simulates
+ * the OTP. This means that we can test everything apart from:
+ *
+ * - The OTP state machines and commands.
+ * - Failure to program bits.
+ */
+static int test_mode;
+module_param(test_mode, bool, S_IRUSR);
+MODULE_PARM_DESC(test_mode,
+ "Run in test mode (use a memory buffer rather than OTP");
+
+/*
+ * This is the maximum number of times to try and soak a failed bit. We get
+ * this from the Sidense documentation. After 16 attempts it is very unlikely
+ * that anything will change.
+ */
+#define MAX_PROGRAM_RETRIES 16
+
+#define OTP_MACRO_CMD_REG_OFFSET 0x00
+#define OTP_MACRO_STATUS_REG_OFFSET 0x04
+#define OTP_MACRO_CONFIG_REG_OFFSET 0x08
+#define OTP_MACRO_ADDR_REG_OFFSET 0x0C
+#define OTP_MACRO_D_LO_REG_OFFSET 0x10
+#define OTP_MACRO_D_HI_REG_OFFSET 0x14
+#define OTP_MACRO_Q_LO_REG_OFFSET 0x20
+#define OTP_MACRO_Q_HI_REG_OFFSET 0x24
+#define OTP_MACRO_Q_MR_REG_OFFSET 0x28
+#define OTP_MACRO_Q_MRAB_REG_OFFSET 0x2C
+#define OTP_MACRO_Q_SR_LO_REG_OFFSET 0x30
+#define OTP_MACRO_Q_SR_HI_REG_OFFSET 0x34
+#define OTP_MACRO_Q_RR_LO_REG_OFFSET 0x38
+#define OTP_MACRO_Q_RR_HI_REG_OFFSET 0x3C
+#define OTP_MACRO_TIME_RD_REG_OFFSET 0x40
+#define OTP_MACRO_TIME_WR_REG_OFFSET 0x44
+#define OTP_MACRO_TIME_PGM_REG_OFFSET 0x48
+#define OTP_MACRO_TIME_PCH_REG_OFFSET 0x4C
+#define OTP_MACRO_TIME_CMP_REG_OFFSET 0x50
+#define OTP_MACRO_TIME_RST_REG_OFFSET 0x54
+#define OTP_MACRO_TIME_PWR_REG_OFFSET 0x58
+#define OTP_MACRO_DIRECT_IO_REG_OFFSET 0x5C
+
+/*
+ * The OTP addresses of the special register. This is in the boot
+ * sector and we use words 0 and 2 of sector 0 in redundant format.
+ */
+#define SR_ADDRESS_0 ((1 << 11) | 0x0)
+#define SR_ADDRESS_2 ((1 << 11) | 0x2)
+
+enum otp_command {
+ OTP_COMMAND_IDLE,
+ OTP_COMMAND_WRITE,
+ OTP_COMMAND_PROGRAM,
+ OTP_COMMAND_READ,
+ OTP_COMMAND_WRITE_MR,
+ OTP_COMMAND_PRECHARGE,
+ OTP_COMMAND_COMPARE,
+ OTP_COMMAND_RESET,
+ OTP_COMMAND_RESET_M,
+ OTP_COMMAND_POWER_DOWN,
+ OTP_COMMAND_AUX_UPDATE_A,
+ OTP_COMMAND_AUX_UPDATE_B,
+ OTP_COMMAND_WRITE_PROGRAM,
+ OTP_COMMAND_WRITE_MRA,
+ OTP_COMMAND_WRITE_MRB,
+ OTP_COMMAND_RESET_MR,
+};
+
+/* The control and status registers follow the AXI OTP map. */
+#define OTP_CTRL_BASE 0x4000
+
+/*
+ * The number of words in the OTP device. The device is 16K bytes and the word
+ * size is 64 bits.
+ */
+#define OTP_NUM_WORDS (SZ_16K / OTP_WORD_SIZE)
+
+/*
+ * The OTP device representation. We can have a static structure as there is
+ * only ever one OTP device in a system.
+ *
+ * @iomem: the io memory for the device that should be accessed with the I/O
+ * accessors.
+ * @mem: the 16KB of OTP memory that can be accessed like normal memory. When
+ * we probe, we force the __iomem away so we can read it directly.
+ * @test_mode_sr0, test_mode_sr2 the values of the special register when we're
+ * in test mode.
+ */
+struct pc3x3_otp {
+ struct otp_device *dev;
+ void __iomem *iomem;
+ void *mem;
+ struct clk *clk;
+ u64 test_mode_sr0, test_mode_sr2;
+ unsigned long registered_regions;
+};
+
+static inline void otp_write_reg(struct pc3x3_otp *otp, unsigned reg_num,
+ u32 value)
+{
+ writel(value, otp->iomem + OTP_CTRL_BASE + reg_num);
+}
+
+static inline u32 otp_read_reg(struct pc3x3_otp *otp, unsigned reg_num)
+{
+ return readl(otp->iomem + OTP_CTRL_BASE + reg_num);
+}
+
+static inline u32 otp_read_sr(struct pc3x3_otp *otp)
+{
+ if (test_mode)
+ return otp->test_mode_sr0 | otp->test_mode_sr2;
+
+ return otp_read_reg(otp, OTP_MACRO_Q_SR_LO_REG_OFFSET);
+}
+
+/*
+ * Get the region format. The region format encoding and number of regions are
+ * encoded in the bottom 32 bis of the special register:
+ *
+ * 20: enable redundancy replacement.
+ * [2:0]: AXI address mask - determines the number of address bits to use for
+ * selecting the region to read from.
+ * [m:n]: the format for region X where n := (X * 2) + 4 and m := n + 1.
+ */
+static enum otp_redundancy_fmt
+__pc3x3_otp_region_get_fmt(struct pc3x3_otp *otp, const struct otp_region *region)
+{
+ unsigned shift = (region->region_nr * 2) + 4;
+
+ return (otp_read_sr(otp) >> shift) & 0x3;
+}
+
+static enum otp_redundancy_fmt
+pc3x3_otp_region_get_fmt(struct otp_region *region)
+{
+ struct pc3x3_otp *otp = region->parent->driver_data;
+
+ return __pc3x3_otp_region_get_fmt(otp, region);
+}
+
+/*
+ * Find out how many regions the OTP is partitioned into. This can be 1, 2, 4
+ * or 8.
+ */
+static inline int otp_num_regions(struct pc3x3_otp *otp)
+{
+#define SR_AXI_ADDRESS_MASK 0x7
+ u32 addr_mask;
+ int nr_regions;
+
+ addr_mask = otp_read_sr(otp) & SR_AXI_ADDRESS_MASK;
+
+ if (0 == addr_mask)
+ nr_regions = 1;
+ else if (4 == addr_mask)
+ nr_regions = 2;
+ else if (6 == addr_mask)
+ nr_regions = 4;
+ else if (7 == addr_mask)
+ nr_regions = 8;
+ else
+ nr_regions = 0;
+
+ if (WARN_ON(0 == nr_regions))
+ return -EINVAL;
+
+ return nr_regions;
+}
+
+/*
+ * Find the byte offset of the first word in the region from the base of the
+ * OTP.
+ */
+static unsigned otp_region_base(struct pc3x3_otp *otp,
+ const struct otp_region *region)
+{
+ int num_regions = otp_num_regions(otp);
+ unsigned real_region_sz = SZ_16K / num_regions;
+
+ return (region->region_nr * real_region_sz) / OTP_WORD_SIZE;
+}
+
+static void otp_do_command(struct pc3x3_otp *otp, enum otp_command cmd)
+{
+ otp_write_reg(otp, OTP_MACRO_CMD_REG_OFFSET, cmd);
+ wmb();
+
+ /*
+ * If we're talking to OTP then we need to wait for the command to
+ * finish.
+ */
+ if (!test_mode)
+ while (otp_read_reg(otp, OTP_MACRO_CMD_REG_OFFSET) !=
+ OTP_COMMAND_IDLE)
+ cpu_relax();
+}
+
+static void otp_write_MR(struct pc3x3_otp *otp, u32 value)
+{
+ /* Load the data register with the new contents. */
+ otp_write_reg(otp, OTP_MACRO_D_LO_REG_OFFSET, value);
+ otp_write_reg(otp, OTP_MACRO_D_HI_REG_OFFSET, 0);
+
+ /* Write the register and wait for the write to complete. */
+ otp_do_command(otp, OTP_COMMAND_WRITE_MR);
+}
+
+/*
+ * Create a write function for a given OTP auxillary mode register. This
+ * writes the auxillary mode register through the mode register then restores
+ * the contents of the mode register.
+ */
+#define OTP_REG_WRITE_FUNCTIONS(_name) \
+static void otp_write_##_name(struct pc3x3_otp *otp, u32 value) \
+{ \
+ u32 mr = otp_read_reg(otp, OTP_MACRO_Q_MR_REG_OFFSET); \
+ \
+ /* Load the data register with the new contents. */ \
+ otp_write_reg(otp, OTP_MACRO_D_LO_REG_OFFSET, value); \
+ otp_write_reg(otp, OTP_MACRO_D_HI_REG_OFFSET, 0); \
+ \
+ /* Write the register and wait for the write to complete. */ \
+ otp_do_command(otp, OTP_COMMAND_WRITE_##_name); \
+ \
+ /* Restore the original value of the MR. */ \
+ otp_write_MR(otp, mr); \
+}
+
+OTP_REG_WRITE_FUNCTIONS(MRA);
+OTP_REG_WRITE_FUNCTIONS(MRB);
+
+/*
+ * Enable the charge pump. This monitors the VPP voltage and waits for it to
+ * reach the correct programming level.
+ *
+ * @enable set to non-zero to enable the charge pump, zero to disable it.
+ */
+static void otp_charge_pump_enable(struct pc3x3_otp *otp, int enable)
+{
+#define OTP_MRA_CHARGE_PUMP_ENABLE_MASK (1 << 12)
+#define OTP_MRA_CHARGE_PUMP_MONITOR_MASK (1 << 15)
+#define OTP_MRA_READ_REFERENCE_LEVEL9_MASK (1 << 9)
+#define OTP_MRA_READ_REFERENCE_LEVEL5_MASK (1 << 5)
+#define OTP_STATUS_VPP_APPLIED (1 << 4)
+ u32 mra = enable ?
+ (OTP_MRA_CHARGE_PUMP_ENABLE_MASK |
+ OTP_MRA_CHARGE_PUMP_MONITOR_MASK |
+ OTP_MRA_READ_REFERENCE_LEVEL9_MASK |
+ OTP_MRA_READ_REFERENCE_LEVEL5_MASK) : 0;
+
+ otp_write_MRA(otp, mra);
+
+ /* Now wait for VPP to reach the correct level. */
+ if (enable && !test_mode) {
+ while (!(otp_read_reg(otp, OTP_MACRO_STATUS_REG_OFFSET) &
+ OTP_STATUS_VPP_APPLIED))
+ cpu_relax();
+ }
+
+ udelay(1);
+}
+
+/*
+ * Read a word from OTP.
+ *
+ * @addr the word address to read from.
+ * @val the destination to store the value in.
+ *
+ * Prerequisites: the OTP must be in single-ended read mode so that we can
+ * correctly read the raw word.
+ */
+static int otp_raw_read_word(struct pc3x3_otp *otp, unsigned addr, u64 *val)
+{
+ if (SR_ADDRESS_0 == addr && test_mode)
+ *val = otp->test_mode_sr0;
+ else if (SR_ADDRESS_2 == addr && test_mode)
+ *val = otp->test_mode_sr2;
+ else {
+ union {
+ u64 d64;
+ u32 d32[2];
+ } converter;
+
+ otp_write_reg(otp, OTP_MACRO_ADDR_REG_OFFSET, addr);
+ otp_do_command(otp, OTP_COMMAND_READ);
+
+ converter.d32[0] = otp_read_reg(otp, OTP_MACRO_Q_LO_REG_OFFSET);
+ converter.d32[1] = otp_read_reg(otp, OTP_MACRO_Q_HI_REG_OFFSET);
+
+ if (!test_mode)
+ *val = converter.d64;
+ else
+ memcpy(val, otp->mem + addr * sizeof(u64), sizeof(u64));
+ }
+
+ return 0;
+}
+
+/*
+ * Program a word of OTP to a raw address. This will program an absolute value
+ * into the OTP so if the current word needs to be modified then this needs to
+ * be done with a read-modify-write cycle with the read-modify handled above.
+ *
+ * The actual write operation can't fail here but we don't do any verification
+ * to make sure that the correct data got written. That must be handled by the
+ * layer above.
+ */
+static void otp_raw_program_word(struct pc3x3_otp *otp, unsigned addr, u64 v)
+{
+ unsigned bit_offs;
+ u64 tmp;
+ int set_to_program = addr & 1 ? 0 : 1;
+
+ if (test_mode) {
+ if (addr != SR_ADDRESS_0 && addr != SR_ADDRESS_2) {
+ u64 old;
+
+ if (otp_raw_read_word(otp, addr, &old))
+ return;
+
+ v = (addr & 1) ? old & ~v : old | v;
+
+ memcpy(otp->mem + (addr * OTP_WORD_SIZE), &v, sizeof(v));
+ } else {
+ /*
+ * The special register OTP values are stored in the
+ * boot rows that live outside of the 16KB of normal
+ * OTP so we can't address them directly.
+ */
+ if (SR_ADDRESS_0 == addr)
+ otp->test_mode_sr0 |= v;
+ else
+ otp->test_mode_sr2 |= v;
+ }
+ }
+
+ /* Set the address of the word that we're writing. */
+ otp_write_reg(otp, OTP_MACRO_ADDR_REG_OFFSET, addr);
+
+ for (bit_offs = 0; v && bit_offs < 64; ++bit_offs, v >>= 1) {
+ if (!(v & 0x1))
+ continue;
+
+ tmp = set_to_program ? ~(1LLU << bit_offs) :
+ (1LLU << bit_offs);
+ otp_write_reg(otp, OTP_MACRO_D_LO_REG_OFFSET,
+ (u32)tmp & 0xFFFFFFFF);
+ otp_write_reg(otp, OTP_MACRO_D_HI_REG_OFFSET,
+ (u32)(tmp >> 32) & 0xFFFFFFFF);
+
+ /* Start programming the bit and wait for it to complete. */
+ otp_do_command(otp, OTP_COMMAND_WRITE_PROGRAM);
+ }
+}
+
+static inline void otp_set_program_pulse_len(struct pc3x3_otp *otp,
+ unsigned len)
+{
+#define OTP_TIME_PGM_PULSE_MASK 0x7FF
+ u32 v = otp_read_reg(otp, OTP_MACRO_TIME_PGM_REG_OFFSET);
+ v &= ~OTP_TIME_PGM_PULSE_MASK;
+ v |= len;
+ otp_write_reg(otp, OTP_MACRO_TIME_PGM_REG_OFFSET, v);
+}
+
+/*
+ * Write a raw word in OTP. This will program a word into OTP memory and do
+ * any read-modify-write that is neccessary. For example if address 0 contains
+ * 0x00ef, then writing 0xbe00 will result in address 0 containing 0xbeef.
+ * This does not handle redundancy - this should be done at a higher level.
+ *
+ * @addr the word address to write to.
+ * @val the value to program into the OTP.
+ *
+ * Prerequisites: the OTP must be in single-ended read mode so that we can
+ * correctly verify the word.
+ */
+static int otp_raw_write_word(struct pc3x3_otp *otp,
+ unsigned addr, u64 val)
+{
+ /* The status of the last command. 1 == success. */
+#define OTP_STATUS_LCS (1 << 1)
+
+#define OTP_MR_SELF_TIMING (1 << 2)
+#define OTP_MR_PROGRAMMABLE_DELAY (1 << 5)
+#define OTP_MR_PROGRAMMABLE_DELAY_CONTROL (1 << 8)
+
+#define OTP_MRB_VREF_ADJUST_0 (1 << 0)
+#define OTP_MRB_VREF_ADJUST_1 (1 << 1)
+#define OTP_MRB_VREF_ADJUST_3 (1 << 3)
+#define OTP_MRB_READ_TIMER_DELAY_CONTROL (1 << 12)
+
+ /*
+ * Programming pulse times. For the normal pulse, we use a programming
+ * time of 51.2uS. For a soak pulse where bits fail to program we use
+ * a 1mS pulse.
+ */
+#define OTP_NORMAL_PGM_PULSE_LENGTH 0x50
+#define OTP_SOAK_PGM_PULSE_LENGTH 0x61B
+
+ /*
+ * We program even addresses by setting 0 bits to one and programm odd
+ * addresses by clearing 1 bits to 0.
+ */
+ int set_to_program = addr & 1 ? 0 : 1;
+ int retries = 0, err = 0;
+ u64 orig, v;
+
+ if (otp_raw_read_word(otp, addr, &orig))
+ return -EIO;
+
+ v = set_to_program ? val & ~orig : ~val & orig;
+
+ /* Enable the charge pump to begin programming. */
+ otp_charge_pump_enable(otp, 1);
+ otp_write_MRB(otp, OTP_MRB_VREF_ADJUST_3 |
+ OTP_MRB_READ_TIMER_DELAY_CONTROL);
+ otp_write_MR(otp, OTP_MR_SELF_TIMING | OTP_MR_PROGRAMMABLE_DELAY |
+ OTP_MR_PROGRAMMABLE_DELAY_CONTROL);
+ otp_raw_program_word(otp, addr, v);
+ udelay(1);
+
+ while (retries < MAX_PROGRAM_RETRIES) {
+ /* Update orig so we only reprogram the unprogrammed bits. */
+ if (otp_raw_read_word(otp, addr, &orig)) {
+ err = -EIO;
+ break;
+ }
+
+ /* If we've programmed correctly we have nothing else to do. */
+ if (val == orig) {
+ err = 0;
+ break;
+ }
+
+ /* Reset the mode register. */
+ otp_write_MRB(otp,
+ OTP_MRB_VREF_ADJUST_0 | OTP_MRB_VREF_ADJUST_1 |
+ OTP_MRB_VREF_ADJUST_3 |
+ OTP_MRB_READ_TIMER_DELAY_CONTROL);
+ otp_do_command(otp, OTP_COMMAND_RESET_MR);
+
+ /* Increase the programming pulse length. */
+ otp_set_program_pulse_len(otp, OTP_SOAK_PGM_PULSE_LENGTH);
+
+ /* Work out the failed bits. */
+ v = set_to_program ? val & ~orig : ~val & orig;
+ otp_raw_program_word(otp, addr, v);
+
+ /* Restore the programming pulse length. */
+ otp_set_program_pulse_len(otp, OTP_NORMAL_PGM_PULSE_LENGTH);
+
+ /* Update orig so we only reprogram the unprogrammed bits. */
+ if (otp_raw_read_word(otp, addr, &orig)) {
+ err = -EIO;
+ break;
+ }
+
+ /* If we've programmed correctly we have nothing else to do. */
+ if (val == orig) {
+ err = 0;
+ break;
+ }
+
+ otp_write_MRB(otp, OTP_MRB_VREF_ADJUST_3 |
+ OTP_MRB_READ_TIMER_DELAY_CONTROL);
+ otp_write_MR(otp,
+ OTP_MR_SELF_TIMING | OTP_MR_PROGRAMMABLE_DELAY |
+ OTP_MR_PROGRAMMABLE_DELAY_CONTROL);
+ udelay(1);
+ ++retries;
+ }
+
+ /* Disable the charge pump. We're done now. */
+ otp_charge_pump_enable(otp, 0);
+ otp_write_MRB(otp, 0);
+ otp_write_MRA(otp, 0);
+ otp_do_command(otp, OTP_COMMAND_RESET_MR);
+
+ if (!err && retries >= MAX_PROGRAM_RETRIES) {
+ dev_warn(otp->dev->dev,
+ "writing to raw address %x failed to program after %d attempts\n",
+ addr, MAX_PROGRAM_RETRIES);
+ err = -EBADMSG;
+ }
+
+ return err;
+}
+
+/*
+ * Set the redundancy mode to a specific format. This only affects the
+ * readback through the AXI map and does not store the redundancy format in
+ * the special register.
+ */
+static void __pc3x3_otp_redundancy_mode_set(struct pc3x3_otp *otp,
+ enum otp_redundancy_fmt fmt)
+{
+#define OTP_MR_REDUNDANT_READ_MASK (1 << 4)
+#define OTP_MR_DIFFERENTIAL_READ_MASK (1 << 0)
+ u32 mr_lo = 0;
+
+ if (OTP_REDUNDANCY_FMT_REDUNDANT == fmt)
+ mr_lo |= OTP_MR_REDUNDANT_READ_MASK;
+ else if (OTP_REDUNDANCY_FMT_DIFFERENTIAL == fmt)
+ mr_lo |= OTP_MR_DIFFERENTIAL_READ_MASK;
+ else if (OTP_REDUNDANCY_FMT_DIFFERENTIAL_REDUNDANT == fmt)
+ mr_lo |= OTP_MR_REDUNDANT_READ_MASK |
+ OTP_MR_DIFFERENTIAL_READ_MASK;
+
+ /* Load the data register with the new MR contents. */
+ otp_write_reg(otp, OTP_MACRO_D_LO_REG_OFFSET, mr_lo);
+ otp_write_reg(otp, OTP_MACRO_D_HI_REG_OFFSET, 0);
+
+ /* Write the MR and wait for the write to complete. */
+ otp_do_command(otp, OTP_COMMAND_WRITE_MR);
+}
+
+static int pc3x3_otp_redundancy_mode_set(struct otp_device *dev,
+ enum otp_redundancy_fmt fmt)
+{
+ struct pc3x3_otp *otp = dev->driver_data;
+
+ __pc3x3_otp_redundancy_mode_set(otp, fmt);
+
+ return 0;
+}
+
+/*
+ * Read a word from a specificied OTP region. The address is the user address
+ * for the word to be read and should not take the redundancy into account.
+ */
+static int pc3x3_otp_region_read_word(struct otp_region *region,
+ unsigned long addr, u64 *word)
+{
+ struct pc3x3_otp *otp = region->parent->driver_data;
+ enum otp_redundancy_fmt fmt = __pc3x3_otp_region_get_fmt(otp, region);
+ unsigned num_words, raw_addresses[4];
+ u64 result = 0, raw_values[4];
+ int err = 0;
+
+ /* Enter the single-ended read mode. */
+ __pc3x3_otp_redundancy_mode_set(otp, OTP_REDUNDANCY_FMT_SINGLE_ENDED);
+
+ /*
+ * If we're running with real OTP then the read is simple, just copy
+ * it from the AXI map.
+ */
+ if (!test_mode) {
+ memcpy(word, otp->mem + (otp_region_base(otp, region) + addr) *
+ OTP_WORD_SIZE, sizeof(*word));
+ return 0;
+ }
+
+ /*
+ * If we're in test mode then this is slightly more complicated. We
+ * need to decode the address into the raw address(s) that the block
+ * uses and handle the redundancy format. This allows us to test that
+ * we've programmed all of the redundant words in the correct format.
+ */
+ switch (fmt) {
+ case OTP_REDUNDANCY_FMT_SINGLE_ENDED:
+ num_words = 1;
+ raw_addresses[0] = otp_region_base(otp, region) + addr;
+ otp_raw_read_word(otp, raw_addresses[0], &raw_values[0]);
+ result = raw_values[0];
+ break;
+
+ case OTP_REDUNDANCY_FMT_REDUNDANT:
+ num_words = 2;
+ raw_addresses[0] = otp_region_base(otp, region) +
+ (((addr & 0xFFFE) << 1) | (addr & 1));
+ raw_addresses[1] = otp_region_base(otp, region) +
+ (((addr & 0xFFFE) << 1) | (addr & 1) | 2);
+ otp_raw_read_word(otp, raw_addresses[0], &raw_values[0]);
+ otp_raw_read_word(otp, raw_addresses[1], &raw_values[1]);
+ result = raw_values[0] | raw_values[1];
+ break;
+
+ case OTP_REDUNDANCY_FMT_DIFFERENTIAL:
+ num_words = 2;
+ raw_addresses[0] = otp_region_base(otp, region) +
+ (((addr & 0xFFFF) << 1));
+ raw_addresses[1] = otp_region_base(otp, region) +
+ (((addr & 0xFFFF) << 1) | 1);
+ otp_raw_read_word(otp, raw_addresses[0], &raw_values[0]);
+ otp_raw_read_word(otp, raw_addresses[1], &raw_values[1]);
+ result = raw_values[0] | ~raw_values[1];
+ break;
+
+ case OTP_REDUNDANCY_FMT_DIFFERENTIAL_REDUNDANT:
+ num_words = 4;
+ raw_addresses[0] = otp_region_base(otp, region) +
+ ((addr & 0xFFFF) << 2);
+ raw_addresses[1] = otp_region_base(otp, region) +
+ (((addr & 0xFFFF) << 2) | 0x1);
+ raw_addresses[2] = otp_region_base(otp, region) +
+ (((addr & 0xFFFF) << 2) | 0x2);
+ raw_addresses[3] = otp_region_base(otp, region) +
+ (((addr & 0xFFFF) << 2) | 0x3);
+ otp_raw_read_word(otp, raw_addresses[0], &raw_values[0]);
+ otp_raw_read_word(otp, raw_addresses[1], &raw_values[1]);
+ otp_raw_read_word(otp, raw_addresses[2], &raw_values[2]);
+ otp_raw_read_word(otp, raw_addresses[3], &raw_values[3]);
+ result = (raw_values[0] | ~raw_values[1]) |
+ (raw_values[2] | ~raw_values[3]);
+ break;
+
+ default:
+ err = -EINVAL;
+ }
+
+ *word = result;
+
+ return err;
+}
+
+/*
+ * Write a data word to an OTP region. The value will be used in a
+ * read-modify-write cycle to ensure that bits can't be flipped if they have
+ * already programmed (the hardware isn't capable of this). This also takes
+ * into account the redundancy addressing and formatting.
+ */
+static int pc3x3_otp_region_write_word(struct otp_region *region,
+ unsigned long addr,
+ u64 word)
+{
+ struct pc3x3_otp *otp = region->parent->driver_data;
+ enum otp_redundancy_fmt fmt = __pc3x3_otp_region_get_fmt(otp, region);
+ unsigned i, num_words, raw_addresses[4];
+ u64 result;
+ int err = 0;
+
+ /* Enter the single-ended read mode. */
+ __pc3x3_otp_redundancy_mode_set(otp, OTP_REDUNDANCY_FMT_SINGLE_ENDED);
+
+ /*
+ * Work out what raw addresses and values we need to write into the
+ * OTP to make sure that the value we want gets read back out
+ * correctly.
+ */
+ switch (fmt) {
+ case OTP_REDUNDANCY_FMT_SINGLE_ENDED:
+ num_words = 1;
+ raw_addresses[0] = otp_region_base(otp, region) + addr;
+ break;
+
+ case OTP_REDUNDANCY_FMT_REDUNDANT:
+ num_words = 2;
+ raw_addresses[0] = otp_region_base(otp, region) +
+ (((addr & 0xFFFE) << 1) | (addr & 1));
+ raw_addresses[1] = otp_region_base(otp, region) +
+ (((addr & 0xFFFE) << 1) | (addr & 1) | 2);
+ break;
+
+ case OTP_REDUNDANCY_FMT_DIFFERENTIAL:
+ num_words = 2;
+ raw_addresses[0] = otp_region_base(otp, region) +
+ (((addr & 0xFFFF) << 1));
+ raw_addresses[1] = otp_region_base(otp, region) +
+ (((addr & 0xFFFF) << 1) | 1);
+ break;
+
+ case OTP_REDUNDANCY_FMT_DIFFERENTIAL_REDUNDANT:
+ num_words = 4;
+ raw_addresses[0] = otp_region_base(otp, region) +
+ ((addr & 0xFFFF) << 2);
+ raw_addresses[1] = otp_region_base(otp, region) +
+ (((addr & 0xFFFF) << 2) | 0x1);
+ raw_addresses[2] = otp_region_base(otp, region) +
+ (((addr & 0xFFFF) << 2) | 0x2);
+ raw_addresses[3] = otp_region_base(otp, region) +
+ (((addr & 0xFFFF) << 2) | 0x3);
+ break;
+
+ default:
+ err = -EINVAL;
+ goto out;
+ }
+
+ /*
+ * Verify the raw words. If we are doing strict programming then they
+ * must all program correctly. If we aren't doing strict programming
+ * then allow failures to 'slip through' for now. If the word can be
+ * read back correctly in the redundant mode then that's fine with the
+ * user.
+ */
+ for (i = 0; i < num_words; ++i)
+ err = otp_raw_write_word(otp, raw_addresses[i], word);
+ if (err && otp_strict_programming_enabled())
+ goto out;
+
+ /* Go back to the real redundancy mode and verify the whole word. */
+ __pc3x3_otp_redundancy_mode_set(otp, fmt);
+
+ if (region->ops->read_word(region, addr, &result)) {
+ err = -EIO;
+ goto out;
+ }
+
+ /*
+ * Now check that the word has been correctly programmed with the
+ * region formatting.
+ */
+ if (result == word) {
+ err = 0;
+ } else {
+ dev_warn(®ion->dev,
+ "word at address %lx write failed %llx != %llx (result != expected)\n",
+ addr, result, word);
+ err = -EBADMSG;
+ }
+
+out:
+ return err;
+}
+
+/*
+ * Write the special register. In PC3X3, we only use the lower 32 bits of the
+ * SR to indicate the partitioning and the region formats so we do a
+ * read-modify-write of the whole 64 bit value.
+ */
+static int otp_write_sr(struct pc3x3_otp *otp, u32 sr_lo)
+{
+ if (otp_raw_write_word(otp, SR_ADDRESS_0, sr_lo)) {
+ dev_warn(otp->dev->dev,
+ "failed to write special register (word 0)\n");
+ return -EIO;
+ }
+
+ if (otp_raw_write_word(otp, SR_ADDRESS_2, sr_lo)) {
+ dev_warn(otp->dev->dev,
+ "failed to write special register (word 0)\n");
+ return -EIO;
+ }
+
+ /*
+ * Reset the OTP so that when we read the special register again we
+ * get the value that we've just written.
+ */
+ otp_do_command(otp, OTP_COMMAND_RESET);
+
+ return 0;
+}
+
+
+static int pc3x3_otp_region_set_fmt(struct otp_region *region,
+ enum otp_redundancy_fmt new_fmt)
+{
+ int err;
+ struct pc3x3_otp *otp = region->parent->driver_data;
+ enum otp_redundancy_fmt fmt = __pc3x3_otp_region_get_fmt(otp, region);
+ unsigned shift = (region->region_nr * 2) + 4;
+ unsigned long sr;
+
+ /*
+ * We can't clear format bits so we can only do certain transitions.
+ * It is possible to go from redundant to differential-redundant or
+ * differential to differential redundant but if the region is already
+ * programmed this could give unexpected results. However, the user
+ * _might_ know what they're doing.
+ */
+ if (fmt & ~new_fmt) {
+ err = -EINVAL;
+ goto out;
+ }
+ if (fmt == new_fmt) {
+ err = 0;
+ goto out;
+ }
+
+ sr = otp_read_sr(otp);
+ sr |= new_fmt << shift;
+ err = otp_write_sr(otp, sr);
+
+out:
+ return err;
+}
+
+/*
+ * Find out how big the region is. We have a 16KB device which can be split
+ * equally into 1, 2, 4 or 8 regions. If a partition is redundant or
+ * differential redundancy then this is 2 bits of storage per data bit so half
+ * the size. For differential-redundant redundancy, 1 bit of data takes 4 bits
+ * of storage so divide by 4.
+ */
+static ssize_t pc3x3_otp_region_get_size(struct otp_region *region)
+{
+ struct pc3x3_otp *otp = region->parent->driver_data;
+ int num_regions = otp_num_regions(otp);
+ enum otp_redundancy_fmt fmt = __pc3x3_otp_region_get_fmt(otp, region);
+ size_t region_sz;
+
+ region_sz = (SZ_16K / num_regions);
+ if (OTP_REDUNDANCY_FMT_REDUNDANT == fmt ||
+ OTP_REDUNDANCY_FMT_DIFFERENTIAL == fmt)
+ region_sz /= 2;
+ else if (OTP_REDUNDANCY_FMT_DIFFERENTIAL_REDUNDANT == fmt)
+ region_sz /= 4;
+
+ return region_sz;
+}
+
+static const struct otp_region_ops pc3x3_region_ops = {
+ .set_fmt = pc3x3_otp_region_set_fmt,
+ .get_fmt = pc3x3_otp_region_get_fmt,
+ .write_word = pc3x3_otp_region_write_word,
+ .read_word = pc3x3_otp_region_read_word,
+ .get_size = pc3x3_otp_region_get_size,
+};
+
+static int pc3x3_otp_register_regions(struct pc3x3_otp *dev,
+ bool need_unlocked)
+{
+ struct otp_device *otp = dev->dev;
+ int err = 0, i, nr_regions = otp->ops->get_nr_regions(otp);
+
+ for (i = 0; i < nr_regions; ++i) {
+ struct otp_region *region;
+
+ if (test_and_set_bit(i, &dev->registered_regions))
+ continue;
+
+ region = need_unlocked ?
+ otp_region_alloc_unlocked(otp, &pc3x3_region_ops, i) :
+ otp_region_alloc(otp, &pc3x3_region_ops, i);
+ if (IS_ERR(region)) {
+ err = PTR_ERR(region);
+ break;
+ }
+ }
+
+ return err;
+}
+
+static int pc3x3_otp_set_nr_regions(struct otp_device *dev, int nr_regions)
+{
+ struct pc3x3_otp *otp = dev->driver_data;
+ unsigned long sr = otp_read_sr(otp);
+ u32 new_mask, addr_mask = sr & SR_AXI_ADDRESS_MASK;
+ int err = 0;
+
+ if (1 == nr_regions)
+ new_mask = 0;
+ else if (2 == nr_regions)
+ new_mask = 4;
+ else if (4 == nr_regions)
+ new_mask = 6;
+ else if (8 == nr_regions)
+ new_mask = 7;
+ else
+ err = -EINVAL;
+
+ if (err)
+ goto out;
+
+ /*
+ * Check that we aren't trying to clear any bits and reduce the number
+ * of regions. This is OTP so we can only increase.
+ */
+ if (addr_mask & ~new_mask) {
+ err = -EINVAL;
+ goto out;
+ }
+
+ if (addr_mask == new_mask) {
+ err = 0;
+ goto out;
+ }
+
+ err = otp_write_sr(otp, sr | new_mask);
+ if (err)
+ goto out;
+
+ err = pc3x3_otp_register_regions(otp, true);
+
+out:
+ return err;
+}
+
+static int pc3x3_otp_get_nr_regions(struct otp_device *dev)
+{
+ struct pc3x3_otp *otp = dev->driver_data;
+ unsigned long sr = otp_read_sr(otp);
+ u32 addr_mask = sr & SR_AXI_ADDRESS_MASK;
+
+ if (0 == addr_mask)
+ return 1;
+ else if (4 == addr_mask)
+ return 2;
+ else if (6 == addr_mask)
+ return 4;
+ else if (7 == addr_mask)
+ return 8;
+
+ return -EINVAL;
+}
+
+static const struct otp_device_ops pc3x3_otp_ops = {
+ .name = "PC3X3",
+ .owner = THIS_MODULE,
+ .get_nr_regions = pc3x3_otp_get_nr_regions,
+ .set_nr_regions = pc3x3_otp_set_nr_regions,
+ .set_fmt = pc3x3_otp_redundancy_mode_set,
+};
+
+static int __devinit otp_probe(struct platform_device *pdev)
+{
+ int err;
+ struct resource *mem = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+ struct otp_device *otp;
+ struct pc3x3_otp *pc3x3_dev;
+
+ if (!mem) {
+ dev_err(&pdev->dev, "no i/o memory\n");
+ return -ENXIO;
+ }
+
+ if (!devm_request_mem_region(&pdev->dev, mem->start,
+ resource_size(mem), "otp")) {
+ dev_err(&pdev->dev, "unable to request i/o memory\n");
+ return -EBUSY;
+ }
+
+ pc3x3_dev = devm_kzalloc(&pdev->dev, sizeof(*pc3x3_dev), GFP_KERNEL);
+ if (!pc3x3_dev)
+ return -ENOMEM;
+
+ if (test_mode) {
+ u64 *p = devm_kzalloc(&pdev->dev, SZ_16K + SZ_1K, GFP_KERNEL);
+ int i;
+
+ if (!p) {
+ err = -ENOMEM;
+ goto out;
+ }
+
+ pc3x3_dev->mem = p;
+ pc3x3_dev->iomem = (void __force __iomem *)p;
+
+ for (i = 0; (u8 *)p < (u8 *)pc3x3_dev->mem + SZ_16K + SZ_1K;
+ ++p, ++i)
+ *p = (i & 1) ? ~0LLU : 0LLU;
+ } else {
+ pc3x3_dev->iomem = devm_ioremap(&pdev->dev, mem->start,
+ resource_size(mem));
+ if (!pc3x3_dev->iomem) {
+ err = -ENOMEM;
+ goto out;
+ }
+ pc3x3_dev->mem = (void __force *)pc3x3_dev->iomem;
+ }
+
+ pc3x3_dev->clk = clk_get(&pdev->dev, NULL);
+ if (IS_ERR(pc3x3_dev->clk)) {
+ dev_err(&pdev->dev, "device has no clk\n");
+ err = PTR_ERR(pc3x3_dev->clk);
+ goto out;
+ }
+ clk_enable(pc3x3_dev->clk);
+
+ otp = otp_device_alloc(&pdev->dev, &pc3x3_otp_ops, SZ_16K);
+ if (IS_ERR(otp)) {
+ err = PTR_ERR(otp);
+ goto out_clk_disable;
+ }
+ otp->driver_data = pc3x3_dev;
+
+ pc3x3_dev->dev = otp;
+ platform_set_drvdata(pdev, pc3x3_dev);
+
+ err = pc3x3_otp_register_regions(pc3x3_dev, false);
+ if (err)
+ goto out_unregister;
+
+ goto out;
+
+out_unregister:
+ otp_device_unregister(otp);
+out_clk_disable:
+ clk_disable(pc3x3_dev->clk);
+ clk_put(pc3x3_dev->clk);
+out:
+ return err;
+}
+
+static int __devexit otp_remove(struct platform_device *pdev)
+{
+ struct pc3x3_otp *otp = platform_get_drvdata(pdev);
+
+ otp_device_unregister(otp->dev);
+ clk_disable(otp->clk);
+ clk_put(otp->clk);
+
+ return 0;
+}
+
+#ifdef CONFIG_PM
+static int otp_suspend(struct device *dev)
+{
+ struct platform_device *pdev = to_platform_device(dev);
+ struct pc3x3_otp *otp = platform_get_drvdata(pdev);
+
+ otp_write_reg(otp, OTP_MACRO_CMD_REG_OFFSET, OTP_COMMAND_POWER_DOWN);
+ clk_disable(otp->clk);
+
+ return 0;
+}
+
+static int otp_resume(struct device *dev)
+{
+ struct platform_device *pdev = to_platform_device(dev);
+ struct pc3x3_otp *otp = platform_get_drvdata(pdev);
+
+ clk_enable(otp->clk);
+ otp_write_reg(otp, OTP_MACRO_CMD_REG_OFFSET, OTP_COMMAND_IDLE);
+
+ return 0;
+}
+#else /* CONFIG_PM */
+#define otp_suspend NULL
+#define otp_resume NULL
+#endif /* CONFIG_PM */
+
+static const struct dev_pm_ops otp_pm_ops = {
+ .suspend = otp_suspend,
+ .resume = otp_resume,
+};
+
+static struct platform_driver otp_driver = {
+ .remove = __devexit_p(otp_remove),
+ .driver = {
+ .name = "picoxcell-otp-pc3x3",
+ .pm = &otp_pm_ops,
+ },
+};
+
+static int __init pc3x3_otp_init(void)
+{
+ return platform_driver_probe(&otp_driver, otp_probe);
+}
+module_init(pc3x3_otp_init);
+
+static void __exit pc3x3_otp_exit(void)
+{
+ platform_driver_unregister(&otp_driver);
+}
+module_exit(pc3x3_otp_exit);
+
+MODULE_LICENSE("GPL");
+MODULE_AUTHOR("Jamie Iles");
+MODULE_DESCRIPTION("OTP memory driver for Picochip PC3X3 devices");
--
1.7.4
--
To unsubscribe from this list: send the line "unsubscribe linux-kernel" in
the body of a message to majordomo@...r.kernel.org
More majordomo info at http://vger.kernel.org/majordomo-info.html
Please read the FAQ at http://www.tux.org/lkml/
Powered by blists - more mailing lists