blob: c39f4d0a9d95b9d57db9f5a9d2f6cc0ddc1e055f [file] [log] [blame]
/*
* Freescale LS1024A OTP access
*
* (C) Copyright 2014 Google Inc.
*
* 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 program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston,
* MA 02111-1307 USA
*/
#include <linux/module.h>
#include <linux/proc_fs.h>
#include <linux/seq_file.h>
#include <linux/delay.h>
#include <linux/io.h>
#include <linux/clk.h>
#include <linux/device.h>
#include <linux/platform_device.h>
#define DRIVER_NAME "ls1024a-otp"
#define OTP_CONFIG_LOCK_0 0x00
#define OTP_CONFIG_LOCK_1 0x04
#define OTP_CEB_INPUT 0x0C
#define OTP_RSTB_INPUT 0x10
#define OTP_ADDR_INPUT 0x14
#define OTP_READEN_INPUT 0x18
#define OTP_DATA_OUT_COUNTER 0x4C
#define OTP_DATA_OUTPUT 0x50
struct ls1024a_otp {
struct device *dev;
void __iomem *reg;
struct clk *clk;
spinlock_t lock;
};
/* Taken from repo://barebox/mindspeed/drivers/otp/c2k_otp.c */
static void otp_read(struct ls1024a_otp *otp, u32 offset, u8 *read_data, int size)
{
int i;
unsigned long flags;
u32 read_tmp = 0;
u32 dataout_counter;
u32 axi_clk;
if (NULL == read_data)
return;
if (size <= 0)
return;
axi_clk = clk_get_rate(otp->clk);
/* 70 nSec */
dataout_counter = ((axi_clk * 7 + 99999999) / 100000000) & 0x1FF;
spin_lock_irqsave(&otp->lock, flags);
/* configure the OTP_DATA_OUT_COUNTER for read operation.
70 nsec is needed except for blank check test, in which 1.5 usec is needed.*/
writel(dataout_counter, otp->reg + OTP_DATA_OUT_COUNTER);
/* Unlock the write protection. */
writel(0xEBCF0000, otp->reg + OTP_CONFIG_LOCK_0); /* config lock0 */
writel(0xEBCF1111, otp->reg + OTP_CONFIG_LOCK_1); /* config lock1 */
writel(0x0, otp->reg + OTP_CEB_INPUT);
udelay(1);
/* rstb drive 0 */
writel(0x0, otp->reg + OTP_RSTB_INPUT);
/* Wait for at least 20nsec */
ndelay(20);
/* rstb drive 1 to have pulse */
writel(0x1, otp->reg + OTP_RSTB_INPUT);
/* Wait for at least 1usec */
udelay(1);
/* Write the desired address to the ADDR register */
writel(offset, otp->reg + OTP_ADDR_INPUT);
/* read_enable drive */
writel(0x1, otp->reg + OTP_READEN_INPUT);
/* Wait for at least 70nsec/1.5usec depends on operation type */
ndelay(70);
/* Read First Byte */
read_tmp = readl(otp->reg + OTP_DATA_OUTPUT);
*read_data = read_tmp & 0xFF;
/* For consecutive read */
for(i = 1 ; i < size ; i++)
{
offset = offset + 8;
/* start reading from data out register */
writel(offset, otp->reg + OTP_ADDR_INPUT);
/* Wait for at least 70nsec/1.5usec depends on operation type */
ndelay(70);
read_tmp = readl(otp->reg + OTP_DATA_OUTPUT);
*(read_data + i) = read_tmp & 0xFF;
}
/* reading is done make the read_enable low */
writel(0x0, otp->reg + OTP_READEN_INPUT);
/* lock CEB register, return to standby mode */
writel(0x1, otp->reg + OTP_CEB_INPUT);
spin_unlock_irqrestore(&otp->lock, flags);
}
static int islocked_proc_show(struct seq_file *m, void *v)
{
struct ls1024a_otp *otp = (struct ls1024a_otp *) m->private;
char status_buf[1];
otp_read(otp, 8, status_buf, 1);
seq_printf(m, "%d\n", (*status_buf & 0x2) == 0x2);
return 0;
}
static int islocked_proc_open(struct inode *inode, struct file *filp)
{
struct ls1024a_otp *otp = PDE_DATA(inode);
return single_open(filp, islocked_proc_show, otp);
}
static struct file_operations islocked_proc_fops = {
.owner = THIS_MODULE,
.open = islocked_proc_open,
.read = seq_read,
.llseek = seq_lseek,
.release = single_release,
};
static int ls1024a_otp_probe(struct platform_device *pdev)
{
struct device *dev = &pdev->dev;
struct resource *res;
struct ls1024a_otp *otp;
int rc;
struct proc_dir_entry *otp_dir;
struct proc_dir_entry *islocked_proc_file;
otp = devm_kzalloc(dev, sizeof(*otp), GFP_KERNEL);
if (!otp)
return -ENOMEM;
otp->dev = &pdev->dev;
platform_set_drvdata(pdev, otp);
spin_lock_init(&otp->lock);
res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
otp->reg = devm_ioremap_resource(dev, res);
if (IS_ERR(otp->reg)) {
rc = PTR_ERR(otp->reg);
goto err1;
}
otp->clk = devm_clk_get(&pdev->dev, NULL);
if (IS_ERR(otp->clk)) {
rc = PTR_ERR(otp->clk);
goto err2;
}
rc = clk_prepare_enable(otp->clk);
if (rc)
goto err2;
otp_dir = proc_mkdir("otp", NULL);
if (otp_dir == NULL) {
dev_err(otp->dev, "Unable to create /proc/otp directory\n");
rc = -ENOMEM;
goto err3;
}
islocked_proc_file = proc_create_data("islocked", 0, otp_dir, &islocked_proc_fops, otp);
if (islocked_proc_file == NULL) {
dev_err(otp->dev, "Unable to create /proc/otp/islocked\n");
rc = -ENOMEM;
goto err4;
}
return 0;
err4:
remove_proc_entry("otp", NULL);
err3:
clk_disable_unprepare(otp->clk);
err2:
devm_iounmap(dev, otp->reg);
err1:
platform_set_drvdata(pdev, NULL);
devm_kfree(dev, otp);
return rc;
}
static const struct of_device_id ls1024a_otp_of_match[] = {
{
.compatible = "fsl,ls1024a-otp",
},
{},
};
MODULE_DEVICE_TABLE(of, ls1024a_otp_of_match);
static struct platform_driver ls1024a_otp_driver = {
.probe = ls1024a_otp_probe,
.driver = {
.name = DRIVER_NAME,
.of_match_table = ls1024a_otp_of_match,
},
};
module_platform_driver(ls1024a_otp_driver);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("Stephen McGruer <smcgruer@google.com>");
MODULE_DESCRIPTION("LS1024A OTP driver");