blob: 6747388bb24f9bde4d0a3a0c5bd897c7bf8a2407 [file] [log] [blame]
/*
* Copyright (c) 2013 Qualcomm Atheros, 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/device.h>
#include <linux/platform_device.h>
#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/resource.h>
#include <linux/console.h>
#include <asm/serial.h>
#include <linux/tty.h>
#include <linux/time.h>
#include <linux/serial_core.h>
#include <linux/serial.h>
#include <linux/serial_8250.h>
#include <linux/miscdevice.h>
#include <asm/delay.h>
#include <atheros.h>
uint32_t
ath_otp_read(uint32_t addr)
{
uint32_t rdata, i = 0;
if (addr >= ATH_OTP_SIZE)
return 0xdeadbeef;
rdata = ath_reg_rd(ATH_OTP_MEM_ADDRESS + addr);
do {
if (i++ > 65536)
return 0xdeadbeef;
udelay(1);
rdata = ath_reg_rd(ATH_OTP_STATUS0_ADDRESS);
} while(OTP_STATUS0_EFUSE_ACCESS_BUSY_GET(rdata) ||
!OTP_STATUS0_EFUSE_READ_DATA_VALID_GET(rdata));
return (ath_reg_rd(ATH_OTP_STATUS1_ADDRESS));
}
EXPORT_SYMBOL(ath_otp_read);
uint32_t
ath_otp_write(uint32_t addr, uint32_t data)
{
if (addr >= ATH_OTP_SIZE)
return 0xdeadbeef;
#define ATH_OTP_MAGIC 0x10ad079
ath_reg_wr(ATH_OTP_LDO_CONTROL, 1);
while ((ath_reg_rd(ATH_OTP_LDO_STATUS) & 1) == 0);
ath_reg_wr(ATH_OTP_INTF0_ADDRESS, ATH_OTP_MAGIC);
ath_reg_wr(ATH_OTP_MEM_ADDRESS + addr, data);
udelay(500);
ath_reg_wr(ATH_OTP_LDO_CONTROL, 0);
while ((ath_reg_rd(ATH_OTP_LDO_STATUS) & 1) != 0);
return 0;
}
EXPORT_SYMBOL(ath_otp_write);
static ssize_t
ath_otp_fop_read(struct file *file, char __user *buf, size_t count, loff_t *ppos)
{
unsigned int val, i, pos = (unsigned int)*ppos;
for (i = 0; i < count; i += sizeof(val), pos += sizeof(val)) {
val = ath_otp_read(pos);
if (val == 0xdeadbeef)
break;
if (copy_to_user(buf + i, &val, sizeof(val)))
return -EFAULT;
}
*ppos += i;
return i;
}
static struct file_operations ath_otp_fops = {
.read = ath_otp_fop_read,
};
static struct miscdevice ath_otp_miscdev = {
ATH_OTP_MINOR,
"otp",
&ath_otp_fops
};
static int __init
ath_otp_init(void)
{
uint32_t rdata, tdata;
/*
* Calculate how many nanoseconds does a ref clock cycle take
* 40MHz -> 25 ns
* 25MHz -> 40 ns
*/
tdata = 1000 / (ath_ref_freq / 1000000);
/*
* Set OTP registers:
* Set OTP_INTF2_PG_STROBE_PW_REG_V
* OTP_INTF2_PG_STROBE_PW_REG_V *
* (how many nanoseconds does a ref clock cycle take)
* = 5000 ns
* 40MHz -> 25 ns --> 5000 / 25 = 1000 (0xC8)
* 25MHz -> 40 ns --> 5000 / 40 = 1000 (0x7D)
*/
tdata = 5000 / tdata;
rdata = ath_reg_rd(ATH_OTP_INTF2_ADDRESS);
rdata &= ~OTP_INTF2_PG_STROBE_PW_REG_V_MASK;
ath_reg_wr(ATH_OTP_INTF2_ADDRESS,
rdata | OTP_INTF2_PG_STROBE_PW_REG_V_SET(tdata));
/*
* Set OTP registers : Set OTP_INTF3_RD_STROBE_PW_REG_V to 8
*/
rdata = ath_reg_rd(ATH_OTP_INTF3_ADDRESS);
rdata &= ~OTP_INTF3_RD_STROBE_PW_REG_V_MASK;
ath_reg_wr(ATH_OTP_INTF3_ADDRESS,
rdata | OTP_INTF3_RD_STROBE_PW_REG_V_SET(8));
/*
* Set OTP registers : Set OTP_PGENB_SETUP_HOLD_TIME to 3
*/
rdata = ath_reg_rd(ATH_OTP_PGENB_SETUP_HOLD_TIME_ADDRESS);
rdata &= ~OTP_PGENB_SETUP_HOLD_TIME_DELAY_MASK;
ath_reg_wr(ATH_OTP_PGENB_SETUP_HOLD_TIME_ADDRESS,
rdata | OTP_PGENB_SETUP_HOLD_TIME_DELAY_SET(3));
printk("%s: Registering OTP ", __func__);
if ((tdata = misc_register(&ath_otp_miscdev))) {
printk("failed %d\n", tdata);
return tdata;
} else {
printk("success\n");
}
return 0;
}
late_initcall(ath_otp_init);