diff options
Diffstat (limited to 'arch/arm/mach-imx/iim.c')
-rw-r--r-- | arch/arm/mach-imx/iim.c | 311 |
1 files changed, 311 insertions, 0 deletions
diff --git a/arch/arm/mach-imx/iim.c b/arch/arm/mach-imx/iim.c new file mode 100644 index 0000000000..0774ebba9c --- /dev/null +++ b/arch/arm/mach-imx/iim.c @@ -0,0 +1,311 @@ +/* + * iim.c - i.MX IIM fusebox driver + * + * Provide an interface for programming and sensing the information that are + * stored in on-chip fuse elements. This functionality is part of the IC + * Identification Module (IIM), which is present on some i.MX CPUs. + * + * Copyright (c) 2010 Baruch Siach <baruch@tkos.co.il>, + * Orex Computed Radiography + * + * 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. + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ + +#include <common.h> +#include <init.h> +#include <driver.h> +#include <xfuncs.h> +#include <errno.h> +#include <param.h> + +#include <asm/io.h> + +#include <mach/iim.h> + +#define DRIVERNAME "imx_iim" + +static unsigned long mac_addr_base; + +static int do_fuse_sense(unsigned long reg_base, unsigned int bank, + unsigned int row) +{ + u8 err, stat; + + if (bank > 7) { + printf("%s: invalid bank number\n", __func__); + return -EINVAL; + } + + if (row > 0x3ff) { + printf("%s: invalid row offset\n", __func__); + return -EINVAL; + } + + /* clear status and error registers */ + writeb(3, reg_base + IIM_STATM); + writeb(0xfe, reg_base + IIM_ERR); + + /* upper and lower address halves */ + writeb((bank << 3) | (row >> 7), reg_base + IIM_UA); + writeb((row << 1) & 0xf8, reg_base + IIM_LA); + + /* start fuse sensing */ + writeb(0x08, reg_base + IIM_FCTL); + + /* wait for sense done */ + while ((readb(reg_base + IIM_STAT) & 0x80) != 0) + ; + + stat = readb(reg_base + IIM_STAT); + writeb(stat, reg_base + IIM_STAT); + + err = readb(reg_base + IIM_ERR); + if (err) { + printf("%s: sense error (0x%02x)\n", __func__, err); + return -EIO; + } + + return readb(reg_base + IIM_SDAT); +} + +static ssize_t imx_iim_read(struct cdev *cdev, void *buf, size_t count, + ulong offset, ulong flags) +{ + ulong size, i; + struct device_d *dev = cdev->dev; + const char *sense_param; + unsigned long explicit_sense = 0; + + if (dev == NULL) + return -EINVAL; + + if ((sense_param = dev_get_param(dev, "explicit_sense_enable"))) + explicit_sense = simple_strtoul(sense_param, NULL, 0); + + size = min((ulong)count, dev->size - offset); + if (explicit_sense) { + for (i = 0; i < size; i++) { + int row_val; + + row_val = do_fuse_sense(dev->parent->map_base, + dev->id, (offset+i)*4); + if (row_val < 0) + return row_val; + ((u8 *)buf)[i] = (u8)row_val; + } + } else { + for (i = 0; i < size; i++) + ((u8 *)buf)[i] = ((u8 *)dev->map_base)[(offset+i)*4]; + } + + return size; +} + +#ifdef CONFIG_IMX_IIM_FUSE_BLOW +static int do_fuse_blow(unsigned long reg_base, unsigned int bank, + unsigned int row, u8 value) +{ + int bit, ret = 0; + u8 err, stat; + + if (bank > 7) { + printf("%s: invalid bank number\n", __func__); + return -EINVAL; + } + + if (row > 0x3ff) { + printf("%s: invalid row offset\n", __func__); + return -EINVAL; + } + + /* clear status and error registers */ + writeb(3, reg_base + IIM_STATM); + writeb(0xfe, reg_base + IIM_ERR); + + /* unprotect fuse programing */ + writeb(0xaa, reg_base + IIM_PREG_P); + + /* upper half address register */ + writeb((bank << 3) | (row >> 7), reg_base + IIM_UA); + + for (bit = 0; bit < 8; bit++) { + if (((value >> bit) & 1) == 0) + continue; + + /* lower half address register */ + writeb(((row << 1) | bit), reg_base + IIM_LA); + + /* start fuse programing */ + writeb(0x71, reg_base + IIM_FCTL); + + /* wait for program done */ + while ((readb(reg_base + IIM_STAT) & 0x80) != 0) + ; + + /* clear program done status */ + stat = readb(reg_base + IIM_STAT); + writeb(stat, reg_base + IIM_STAT); + + err = readb(reg_base + IIM_ERR); + if (err) { + printf("%s: bank %u, row %u, bit %d program error " + "(0x%02x)\n", __func__, bank, row, bit, + err); + ret = -EIO; + goto out; + } + } + +out: + /* protect fuse programing */ + writeb(0, reg_base + IIM_PREG_P); + return ret; +} +#endif /* CONFIG_IMX_IIM_FUSE_BLOW */ + +static ssize_t imx_iim_write(struct cdev *cdev, const void *buf, size_t count, + ulong offset, ulong flags) +{ + ulong size, i; + struct device_d *dev = cdev->dev; + const char *write_param; + unsigned int blow_enable = 0; + + if (dev == NULL) + return -EINVAL; + + if ((write_param = dev_get_param(dev, "permanent_write_enable"))) + blow_enable = simple_strtoul(write_param, NULL, 0); + + size = min((ulong)count, dev->size - offset); +#ifdef CONFIG_IMX_IIM_FUSE_BLOW + if (blow_enable) { + for (i = 0; i < size; i++) { + int ret; + + ret = do_fuse_blow(dev->parent->map_base, dev->id, + (offset+i)*4, ((u8 *)buf)[i]); + if (ret < 0) + return ret; + } + } else +#endif /* CONFIG_IMX_IIM_FUSE_BLOW */ + { + for (i = 0; i < size; i++) + ((u8 *)dev->map_base)[(offset+i)*4] = ((u8 *)buf)[i]; + } + + return size; +} + +static struct file_operations imx_iim_ops = { + .read = imx_iim_read, + .write = imx_iim_write, + .lseek = dev_lseek_default, +}; + +static int imx_iim_blow_enable_set(struct device_d *dev, struct param_d *param, + const char *val) +{ + unsigned long blow_enable; + + if (val == NULL) + return -EINVAL; + + blow_enable = simple_strtoul(val, NULL, 0); + if (blow_enable > 1) + return -EINVAL; + + return dev_param_set_generic(dev, param, blow_enable ? "1" : "0"); +} + +static int imx_iim_probe(struct device_d *dev) +{ + struct imx_iim_platform_data *pdata = dev->platform_data; + + if (pdata) + mac_addr_base = pdata->mac_addr_base; + + return 0; +} + +static int imx_iim_bank_probe(struct device_d *dev) +{ + struct cdev *cdev; + struct device_d *parent; + int err; + + cdev = xzalloc(sizeof (struct cdev)); + dev->priv = cdev; + + cdev->dev = dev; + cdev->ops = &imx_iim_ops; + cdev->size = dev->size; + cdev->name = asprintf(DRIVERNAME "_bank%d", dev->id); + if (cdev->name == NULL) + return -ENOMEM; + + parent = get_device_by_name(DRIVERNAME "0"); + if (parent == NULL) + return -ENODEV; + err = dev_add_child(parent, dev); + if (err < 0) + return err; + +#ifdef CONFIG_IMX_IIM_FUSE_BLOW + err = dev_add_param(dev, "permanent_write_enable", + imx_iim_blow_enable_set, NULL, 0); + if (err < 0) + return err; + err = dev_set_param(dev, "permanent_write_enable", "0"); + if (err < 0) + return err; +#endif /* CONFIG_IMX_IIM_FUSE_BLOW */ + + err = dev_add_param(dev, "explicit_sense_enable", + imx_iim_blow_enable_set, NULL, 0); + if (err < 0) + return err; + err = dev_set_param(dev, "explicit_sense_enable", "0"); + if (err < 0) + return err; + + return devfs_create(cdev); +} + +static struct driver_d imx_iim_driver = { + .name = DRIVERNAME, + .probe = imx_iim_probe, +}; + +static struct driver_d imx_iim_bank_driver = { + .name = DRIVERNAME "_bank", + .probe = imx_iim_bank_probe, +}; + +static int imx_iim_init(void) +{ + register_driver(&imx_iim_driver); + register_driver(&imx_iim_bank_driver); + + return 0; +} +coredevice_initcall(imx_iim_init); + +int imx_iim_get_mac(unsigned char *mac) +{ + int i; + + if (mac_addr_base == 0) + return -EINVAL; + + for (i = 0; i < 6; i++) + mac[i] = readb(mac_addr_base + i*4); + + return 0; +} |