/* * 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 , * 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 #include #include #include #include #include #include #include #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; }