ca13a84ac2
MXS specific devices have some common infrastructure in the kernel known as STMP devices. We have the same in barebox, but with a mxs_ prefix instead of a stmp_ prefix. As some STMP devices are also found on i.MX6 move the common infrastructure out of MXS specific files and use the stmp_ prefix. This is done in preparation for i.MX6 NAND support. Signed-off-by: Sascha Hauer <s.hauer@pengutronix.de>
236 lines
5.5 KiB
C
236 lines
5.5 KiB
C
/*
|
|
* ocotp.c - barebox driver for the On-Chip One Time Programmable for MXS
|
|
*
|
|
* Copyright (C) 2012 by Wolfram Sang, Pengutronix e.K.
|
|
* based on the kernel driver which is
|
|
* Copyright 2010 Freescale Semiconductor, Inc. All Rights Reserved.
|
|
*
|
|
* 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.
|
|
*/
|
|
|
|
#include <common.h>
|
|
#include <init.h>
|
|
#include <driver.h>
|
|
#include <xfuncs.h>
|
|
#include <errno.h>
|
|
#include <param.h>
|
|
#include <fcntl.h>
|
|
#include <malloc.h>
|
|
#include <io.h>
|
|
#include <stmp-device.h>
|
|
#include <clock.h>
|
|
#include <linux/clk.h>
|
|
#include <linux/err.h>
|
|
|
|
#include <mach/generic.h>
|
|
#include <mach/ocotp.h>
|
|
#include <mach/imx-regs.h>
|
|
#include <mach/power.h>
|
|
|
|
#define DRIVERNAME "ocotp"
|
|
|
|
#define OCOTP_CTRL 0x0
|
|
# define OCOTP_CTRL_ADDR_MASK 0x3f
|
|
# define OCOTP_CTRL_BUSY (1 << 8)
|
|
# define OCOTP_CTRL_ERROR (1 << 9)
|
|
# define OCOTP_CTRL_RD_BANK_OPEN (1 << 12)
|
|
# define OCOTP_CTRL_WR_UNLOCK 0x3e770000
|
|
|
|
#define OCOTP_DATA 0x10
|
|
|
|
#define OCOTP_WORD_OFFSET 0x20
|
|
|
|
struct ocotp_priv {
|
|
struct cdev cdev;
|
|
void __iomem *base;
|
|
unsigned int write_enable;
|
|
struct clk *clk;
|
|
};
|
|
|
|
static int mxs_ocotp_wait_busy(struct ocotp_priv *priv)
|
|
{
|
|
uint64_t start = get_time_ns();
|
|
|
|
/* check both BUSY and ERROR cleared */
|
|
while (readl(priv->base + OCOTP_CTRL) & (OCOTP_CTRL_BUSY | OCOTP_CTRL_ERROR))
|
|
if (is_timeout(start, MSECOND))
|
|
return -ETIMEDOUT;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static ssize_t mxs_ocotp_cdev_read(struct cdev *cdev, void *buf, size_t count,
|
|
loff_t offset, ulong flags)
|
|
{
|
|
struct ocotp_priv *priv = cdev->priv;
|
|
void __iomem *base = priv->base;
|
|
size_t size = min((loff_t)count, cdev->size - offset);
|
|
int i;
|
|
|
|
/*
|
|
* clk_enable(hbus_clk) for ocotp can be skipped
|
|
* as it must be on when system is running.
|
|
*/
|
|
|
|
/* try to clear ERROR bit */
|
|
writel(OCOTP_CTRL_ERROR, base + OCOTP_CTRL + STMP_OFFSET_REG_CLR);
|
|
|
|
if (mxs_ocotp_wait_busy(priv))
|
|
return -ETIMEDOUT;
|
|
|
|
/* open OCOTP banks for read */
|
|
writel(OCOTP_CTRL_RD_BANK_OPEN, base + OCOTP_CTRL + STMP_OFFSET_REG_SET);
|
|
|
|
/* approximately wait 32 hclk cycles */
|
|
udelay(1);
|
|
|
|
/* poll BUSY bit becoming cleared */
|
|
if (mxs_ocotp_wait_busy(priv))
|
|
return -ETIMEDOUT;
|
|
|
|
for (i = 0; i < size; i++)
|
|
/* When reading bytewise, we need to hop over the SET/CLR/TGL regs */
|
|
((u8 *)buf)[i] = readb(base + OCOTP_WORD_OFFSET +
|
|
(((i + offset) & 0xfc) << 2) + ((i + offset) & 3));
|
|
|
|
/* close banks for power saving */
|
|
writel(OCOTP_CTRL_RD_BANK_OPEN, base + OCOTP_CTRL + STMP_OFFSET_REG_CLR);
|
|
|
|
return size;
|
|
}
|
|
|
|
static ssize_t mxs_ocotp_cdev_write(struct cdev *cdev, const void *buf, size_t count,
|
|
loff_t offset, ulong flags)
|
|
{
|
|
struct ocotp_priv *priv = cdev->priv;
|
|
void __iomem *base = priv->base;
|
|
unsigned long old_hclk, aligned_offset;
|
|
int old_vddio, num_words, num_bytes, i, ret = 0;
|
|
u8 *work_buf;
|
|
u32 reg;
|
|
|
|
if (!priv->write_enable)
|
|
return -EPERM;
|
|
|
|
/* we can only work on u32, so calc some helpers */
|
|
aligned_offset = offset & ~3UL;
|
|
num_words = DIV_ROUND_UP(offset - aligned_offset + count, 4);
|
|
num_bytes = num_words << 2;
|
|
|
|
/* read in all words which will be modified */
|
|
work_buf = xmalloc(num_bytes);
|
|
|
|
i = mxs_ocotp_cdev_read(cdev, work_buf, num_bytes, aligned_offset, 0);
|
|
if (i != num_bytes) {
|
|
ret = -ENXIO;
|
|
goto free_mem;
|
|
}
|
|
|
|
/* modify read words with to be written data */
|
|
for (i = 0; i < count; i++)
|
|
work_buf[offset - aligned_offset + i] |= ((u8 *)buf)[i];
|
|
|
|
/* prepare system for OTP write */
|
|
old_hclk = clk_get_rate(priv->clk);
|
|
old_vddio = imx_get_vddio();
|
|
|
|
clk_set_rate(priv->clk, 24000000);
|
|
imx_set_vddio(2800000);
|
|
|
|
writel(OCOTP_CTRL_RD_BANK_OPEN, base + OCOTP_CTRL + STMP_OFFSET_REG_CLR);
|
|
|
|
if (mxs_ocotp_wait_busy(priv)) {
|
|
ret = -ETIMEDOUT;
|
|
goto restore_system;
|
|
}
|
|
|
|
/* write word for word via data register */
|
|
for (i = 0; i < num_words; i++) {
|
|
reg = readl(base + OCOTP_CTRL) & ~OCOTP_CTRL_ADDR_MASK;
|
|
reg |= OCOTP_CTRL_WR_UNLOCK | ((aligned_offset >> 2) + i);
|
|
writel(reg, base + OCOTP_CTRL);
|
|
|
|
writel(((u32 *)work_buf)[i], base + OCOTP_DATA);
|
|
|
|
if (mxs_ocotp_wait_busy(priv)) {
|
|
ret = -ETIMEDOUT;
|
|
goto restore_system;
|
|
}
|
|
|
|
mdelay(2);
|
|
}
|
|
|
|
restore_system:
|
|
imx_set_vddio(old_vddio);
|
|
clk_set_rate(priv->clk, old_hclk);
|
|
free_mem:
|
|
free(work_buf);
|
|
|
|
return ret;
|
|
}
|
|
|
|
static struct file_operations mxs_ocotp_ops = {
|
|
.read = mxs_ocotp_cdev_read,
|
|
.lseek = dev_lseek_default,
|
|
};
|
|
|
|
static int mxs_ocotp_probe(struct device_d *dev)
|
|
{
|
|
int err;
|
|
struct ocotp_priv *priv = xzalloc(sizeof (*priv));
|
|
|
|
priv->base = dev_request_mem_region(dev, 0);
|
|
priv->clk = clk_get(dev, NULL);
|
|
if (IS_ERR(priv->clk))
|
|
return PTR_ERR(priv->clk);
|
|
priv->cdev.dev = dev;
|
|
priv->cdev.ops = &mxs_ocotp_ops;
|
|
priv->cdev.priv = priv;
|
|
priv->cdev.size = cpu_is_mx23() ? 128 : 160;
|
|
priv->cdev.name = DRIVERNAME;
|
|
|
|
err = devfs_create(&priv->cdev);
|
|
if (err < 0)
|
|
return err;
|
|
|
|
if (IS_ENABLED(CONFIG_MXS_OCOTP_WRITABLE)) {
|
|
mxs_ocotp_ops.write = mxs_ocotp_cdev_write;
|
|
dev_add_param_bool(dev, "permanent_write_enable",
|
|
NULL, NULL, &priv->write_enable, NULL);
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static struct driver_d mxs_ocotp_driver = {
|
|
.name = DRIVERNAME,
|
|
.probe = mxs_ocotp_probe,
|
|
};
|
|
|
|
static int mxs_ocotp_init(void)
|
|
{
|
|
platform_driver_register(&mxs_ocotp_driver);
|
|
|
|
return 0;
|
|
}
|
|
coredevice_initcall(mxs_ocotp_init);
|
|
|
|
int mxs_ocotp_read(void *buf, int count, int offset)
|
|
{
|
|
struct cdev *cdev;
|
|
int ret;
|
|
|
|
cdev = cdev_open(DRIVERNAME, O_RDONLY);
|
|
if (!cdev)
|
|
return -ENODEV;
|
|
|
|
ret = cdev_read(cdev, buf, count, offset, 0);
|
|
|
|
cdev_close(cdev);
|
|
|
|
return ret;
|
|
}
|