blob: 76b5aa9c14a691f1165fb10c3b8667e06a2239cd [file] [log] [blame]
/*
* Copyright (c) 2013, The Linux Foundation. All rights reserved.
*
* Previously licensed under the ISC license by Qualcomm Atheros, Inc.
*
*
* Permission to use, copy, modify, and/or distribute this software for
* any purpose with or without fee is hereby granted, provided that the
* above copyright notice and this permission notice appear in all
* copies.
*
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL
* WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED
* WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE
* AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL
* DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR
* PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER
* TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
* PERFORMANCE OF THIS SOFTWARE.
*/
/*
* This file was originally distributed by Qualcomm Atheros, Inc.
* under proprietary terms before Copyright ownership was assigned
* to the Linux Foundation.
*/
#include <sys/types.h>
#include <sys/file.h>
#include <sys/ioctl.h>
#include <sys/socket.h>
#include <linux/types.h>
#include <linux/if.h>
#include <linux/wireless.h>
#include <linux/version.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <getopt.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <errno.h>
#include <sys/time.h>
#include <athdefs.h>
#include <a_types.h>
#include "apb_athr_wlan_map.h"
#include "rtc_soc_reg.h"
#include "efuse_reg.h"
#ifndef PATH_MAX
#define PATH_MAX 1024
#endif
/*
* This is a user-level agent which provides diagnostic read/write
* access to Target space. This may be used
* to collect information for analysis
* to read/write Target registers
* etc.
*/
#define DIAG_READ_TARGET 1
#define DIAG_WRITE_TARGET 2
#define DIAG_READ_WORD 3
#define DIAG_WRITE_WORD 4
#define ADDRESS_FLAG 0x001
#define LENGTH_FLAG 0x002
#define PARAM_FLAG 0x004
#define FILE_FLAG 0x008
#define UNUSED0x010 0x010
#define AND_OP_FLAG 0x020
#define BITWISE_OP_FLAG 0x040
#define QUIET_FLAG 0x080
#define OTP_FLAG 0x100
/* dump file mode,x: hex mode; other binary mode. */
#define HEX_FLAG 0x200
#define UNUSED0x400 0x400
#define DEVICE_FLAG 0x800
/* Limit malloc size when reading/writing file */
#define MAX_BUF (8*1024)
unsigned int flag;
const char *progname;
const char commands[] =
"commands and options:\n\
--get --address=<target word address>\n\
--set --address=<target word address> --[value|param]=<value>\n\
--or=<OR-ing value>\n\
--and=<AND-ing value>\n\
--read --address=<target address> --length=<bytes> --file=<filename>\n\
--write --address=<target address> --file=<filename>\n\
--[value|param]=<value>\n\
--otp --read --address=<otp offset> --length=<bytes> --file=<filename>\n\
--otp --write --address=<otp offset> --file=<filename>\n\
--quiet\n\
--device=<device name> (if not default)\n\
The options can also be given in the abbreviated form --option=x or -o x.\n\
The options can be given in any order.";
#define A_ROUND_UP(x, y) ((((x) + ((y) - 1)) / (y)) * (y))
#define quiet() (flag & QUIET_FLAG)
#define nqprintf(args...) if (!quiet()) {printf(args);}
#define min(x,y) ((x) < (y) ? (x) : (y))
void ReadTargetRange(int dev, A_UINT32 address, A_UINT8 *buffer,
A_UINT32 length);
void ReadTargetWord(int dev, A_UINT32 address, A_UINT32 *buffer);
void WriteTargetRange(int dev, A_UINT32 address, A_UINT8 *buffer,
A_UINT32 length);
void WriteTargetWord(int dev, A_UINT32 address, A_UINT32 value);
int ValidWriteOTP(int dev, A_UINT32 address, A_UINT8 *buffer, A_UINT32 length);
static inline void *
MALLOC(int nbytes)
{
void *p= malloc(nbytes);
if (!p)
{
fprintf(stderr, "err -Cannot allocate memory\n");
}
return p;
}
void
usage(void)
{
fprintf(stderr, "usage:\n%s ", progname);
fprintf(stderr, "%s\n", commands);
exit(-1);
}
void
ReadTargetRange(int dev, A_UINT32 address, A_UINT8 *buffer, A_UINT32 length)
{
int nbyte;
unsigned int remaining;
(void)lseek(dev, address, SEEK_SET);
remaining = length;
while (remaining) {
nbyte = read(dev, buffer, (size_t)remaining);
if (nbyte <= 0) {
fprintf(stderr, "err %s failed (nbyte=%d, address=0x%x"
" remaining=%d).\n",
__FUNCTION__, nbyte, address, remaining);
exit(1);
}
remaining -= nbyte;
buffer += nbyte;
address += nbyte;
}
}
void
ReadTargetWord(int dev, A_UINT32 address, A_UINT32 *buffer)
{
ReadTargetRange(dev, address, (A_UINT8 *)buffer, sizeof(*buffer));
}
void
ReadTargetOTP(int dev, A_UINT32 offset, A_UINT8 *buffer, A_UINT32 length)
{
A_UINT32 status_mask;
A_UINT32 otp_status, i;
/* Enable OTP reads */
WriteTargetWord(dev, RTC_SOC_BASE_ADDRESS+OTP_OFFSET, OTP_VDD12_EN_SET(1));
status_mask = OTP_STATUS_VDD12_EN_READY_SET(1);
do {
ReadTargetWord(dev, RTC_SOC_BASE_ADDRESS+OTP_STATUS_OFFSET,
&otp_status);
} while ((otp_status & OTP_STATUS_VDD12_EN_READY_MASK) != status_mask);
/* Conservatively set OTP read timing */
WriteTargetWord(dev, EFUSE_BASE_ADDRESS+RD_STROBE_PW_REG_OFFSET, 6);
/* Read data from OTP */
for (i=0; i<length; i++, offset++) {
A_UINT32 efuse_word;
ReadTargetWord(dev, EFUSE_BASE_ADDRESS+EFUSE_INTF0_OFFSET+(offset<<2),
&efuse_word);
buffer[i] = (A_UINT8)efuse_word;
}
/* Disable OTP */
WriteTargetWord(dev, RTC_SOC_BASE_ADDRESS+OTP_OFFSET, 0);
}
void
WriteTargetRange(int dev, A_UINT32 address, A_UINT8 *buffer, A_UINT32 length)
{
int nbyte;
unsigned int remaining;
A_UINT8 *tbuffer = NULL;
fprintf(stderr, "add 0x%x buff 0x%x\n", address, *((A_UINT32 *)buffer));
remaining = sizeof(address) + length;
while (remaining > sizeof(address)) {
tbuffer = (A_UINT8 *)MALLOC(remaining);
memcpy(tbuffer, (A_UINT8 *)(&address), sizeof(address));
memcpy(tbuffer + sizeof(address), buffer, remaining - sizeof(address));
nbyte = write(dev, tbuffer, (size_t)remaining);
if (nbyte <= 0) {
fprintf(stderr, "err %s failed (nbyte=%d, address=0x%x"
" remaining=%d).\n",
__FUNCTION__, nbyte, address, remaining);
exit(1);
}
remaining -= nbyte;
buffer += nbyte;
address += nbyte;
free(tbuffer);
}
}
void
WriteTargetWord(int dev, A_UINT32 address, A_UINT32 value)
{
A_UINT32 param = value;
WriteTargetRange(dev, address, (A_UINT8 *)&param, sizeof(param));
}
#define BAD_OTP_WRITE(have, want) ((((have) ^ (want)) & (have)) != 0)
/*
* Check if the current contents of OTP and the desired
* contents specified by buffer/length are compatible.
* If we're trying to CLEAR an OTP bit, then this request
* is invalid.
* returns: 0-->INvalid; 1-->valid
*/
int
ValidWriteOTP(int dev, A_UINT32 offset, A_UINT8 *buffer, A_UINT32 length)
{
A_UINT32 i;
A_UINT8 *otp_contents;
otp_contents = (A_UINT8 *)MALLOC(length);
ReadTargetOTP(dev, offset, otp_contents, length);
for (i=0; i<length; i++) {
if (BAD_OTP_WRITE(otp_contents[i], buffer[i])) {
fprintf(stderr, "Abort. Cannot change offset %d from 0x%02x"
" to 0x%02x\n",
offset+i, otp_contents[i], buffer[i]);
return 0;
}
}
return 1;
}
/*
* This is NOT the ideal way to write OTP since it does not handle
* media errors. It's much better to use the otpstream_* API.
* This capability is here to help salvage parts that have previously
* had OTP written.
*/
void
WriteTargetOTP(int dev, A_UINT32 offset, A_UINT8 *buffer, A_UINT32 length)
{
A_UINT32 status_mask;
A_UINT32 otp_status, i;
/* Enable OTP read/write power */
WriteTargetWord(dev, RTC_SOC_BASE_ADDRESS+OTP_OFFSET,
OTP_VDD12_EN_SET(1) | OTP_LDO25_EN_SET(1));
status_mask = OTP_STATUS_VDD12_EN_READY_SET(1) |
OTP_STATUS_LDO25_EN_READY_SET(1);
do {
ReadTargetWord(dev, RTC_SOC_BASE_ADDRESS+OTP_STATUS_OFFSET,
&otp_status);
} while ((otp_status & (OTP_STATUS_VDD12_EN_READY_MASK|
OTP_STATUS_LDO25_EN_READY_MASK)) != status_mask);
/* Conservatively set OTP read/write timing for 110MHz core clock */
WriteTargetWord(dev, EFUSE_BASE_ADDRESS+VDDQ_SETTLE_TIME_REG_OFFSET, 2200);
WriteTargetWord(dev, EFUSE_BASE_ADDRESS+PG_STROBE_PW_REG_OFFSET, 605);
WriteTargetWord(dev, EFUSE_BASE_ADDRESS+RD_STROBE_PW_REG_OFFSET, 6);
/* Enable eFuse for write */
WriteTargetWord(dev, EFUSE_BASE_ADDRESS+EFUSE_WR_ENABLE_REG_OFFSET,
EFUSE_WR_ENABLE_REG_V_SET(1));
WriteTargetWord(dev, EFUSE_BASE_ADDRESS+BITMASK_WR_REG_OFFSET, 0x00);
/* Write data to OTP */
for (i=0; i<length; i++, offset++) {
A_UINT32 efuse_word;
A_UINT32 readback;
int attempt;
#define EFUSE_WRITE_COUNT 3
efuse_word = (A_UINT32)buffer[i];
for (attempt=1; attempt<=EFUSE_WRITE_COUNT; attempt++) {
WriteTargetWord(dev,
EFUSE_BASE_ADDRESS+EFUSE_INTF0_OFFSET+(offset<<2),
efuse_word);
}
/* verify */
ReadTargetWord(dev, EFUSE_BASE_ADDRESS+EFUSE_INTF0_OFFSET+(offset<<2),
&readback);
if (efuse_word != readback) {
fprintf(stderr, "OTP write failed. Offset=%d, Value=0x%x,"
" Readback=0x%x\n", offset, efuse_word, readback);
break;
}
}
/* Disable OTP */
WriteTargetWord(dev, RTC_SOC_BASE_ADDRESS+OTP_OFFSET, 0);
}
unsigned int
parse_address(char *optarg)
{
unsigned int address;
/* may want to add support for symbolic addresses here */
address = strtoul(optarg, NULL, 0);
return address;
}
int
main (int argc, char **argv) {
int c, fd, dev;
int i;
FILE * dump_fd;
unsigned int address = 0, length = 0;
A_UINT32 param;
char filename[PATH_MAX];
char devicename[PATH_MAX];
unsigned int cmd = 0;
A_UINT8 *buffer;
unsigned int bitwise_mask = 0;
progname = argv[0];
if (argc == 1) usage();
flag = 0;
memset(filename, '\0', sizeof(filename));
memset(devicename, '\0', sizeof(devicename));
while (1) {
int option_index = 0;
static struct option long_options[] = {
{"address", 1, NULL, 'a'},
{"and", 1, NULL, 'n'},
{"device", 1, NULL, 'D'},
{"get", 0, NULL, 'g'},
{"file", 1, NULL, 'f'},
{"length", 1, NULL, 'l'},
{"or", 1, NULL, 'o'},
{"otp", 0, NULL, 'O'},
{"param", 1, NULL, 'p'},
{"quiet", 0, NULL, 'q'},
{"read", 0, NULL, 'r'},
{"set", 0, NULL, 's'},
{"value", 1, NULL, 'p'},
{"write", 0, NULL, 'w'},
{"hex", 0, NULL, 'x'},
{0, 0, 0, 0}
};
c = getopt_long (argc, argv, "xrwgsqOf:l:a:p:c:n:o:D:",
long_options, &option_index);
if (c == -1)
break;
switch (c) {
case 'r':
cmd = DIAG_READ_TARGET;
break;
case 'w':
cmd = DIAG_WRITE_TARGET;
break;
case 'g':
cmd = DIAG_READ_WORD;
break;
case 's':
cmd = DIAG_WRITE_WORD;
break;
case 'f':
memset(filename, '\0', sizeof(filename));
snprintf(filename, sizeof(filename), "%s", optarg);
flag |= FILE_FLAG;
break;
case 'l':
length = parse_address(optarg);
flag |= LENGTH_FLAG;
break;
case 'a':
address = parse_address(optarg);
flag |= ADDRESS_FLAG;
break;
case 'p':
param = strtoul(optarg, NULL, 0);
flag |= PARAM_FLAG;
break;
case 'n':
flag |= PARAM_FLAG | AND_OP_FLAG | BITWISE_OP_FLAG;
bitwise_mask = strtoul(optarg, NULL, 0);
break;
case 'o':
flag |= PARAM_FLAG | BITWISE_OP_FLAG;
bitwise_mask = strtoul(optarg, NULL, 0);
break;
case 'O':
flag |= OTP_FLAG;
break;
case 'q':
flag |= QUIET_FLAG;
break;
case 'D':
snprintf(devicename, sizeof(devicename), "%s%s", optarg,
"/athdiag");
flag |= DEVICE_FLAG;
break;
case 'x':
flag |= HEX_FLAG;
break;
default:
fprintf(stderr, "Cannot understand '%s'\n", argv[option_index]);
usage();
}
}
for (;;) {
/* DIAG uses a sysfs special file which may be auto-detected */
if (!(flag & DEVICE_FLAG)) {
FILE *find_dev;
size_t nbytes;
/*
* Convenience: if no device was specified on the command
* line, try to figure it out. Typically there's only a
* single device anyway.
*/
find_dev = popen("find /proc -name athdiagpfs | head -1", "r");
if (find_dev) {
nbytes=fread(devicename, 1, sizeof(devicename), find_dev);
pclose(find_dev);
if (nbytes > 15) {
/* auto-detect possibly successful */
devicename[nbytes-1]='\0'; /* replace \n with 0 */
} else {
snprintf(devicename, sizeof(devicename), "%s",
"unknown_DIAG_device");
}
}
}
dev = open(devicename, O_RDWR);
if (dev >= 0) {
break; /* successfully opened diag special file */
} else {
fprintf(stderr, "err %s failed (%d) to open DIAG file (%s)\n",
__FUNCTION__, errno, devicename);
exit(1);
}
}
switch(cmd)
{
case DIAG_READ_TARGET:
if ((flag & (ADDRESS_FLAG | LENGTH_FLAG | FILE_FLAG)) ==
(ADDRESS_FLAG | LENGTH_FLAG | FILE_FLAG))
{
if (((int)(dump_fd = fopen(filename, "wb+"))) < 0)
{
fprintf(stderr, "err %s cannot create/open output file (%s)\n",
__FUNCTION__, filename);
exit(1);
}
buffer = (A_UINT8 *)MALLOC(MAX_BUF);
nqprintf(
"DIAG Read Target (address: 0x%x, length: %d,"
" filename: %s)\n", address, length, filename);
{
unsigned int remaining = length;
if(flag & HEX_FLAG)
{
if (flag & OTP_FLAG) {
fprintf(dump_fd,"target otp dump area"
" [0x%08x - 0x%08x]",address,address+length);
} else {
fprintf(dump_fd,"target mem dump area"
" [0x%08x - 0x%08x]",address,address+length);
}
}
while (remaining)
{
length = (remaining > MAX_BUF) ? MAX_BUF : remaining;
if (flag & OTP_FLAG) {
ReadTargetOTP(dev, address, buffer, length);
} else {
ReadTargetRange(dev, address, buffer, length);
}
if(flag & HEX_FLAG)
{
for(i=0;i<(int)length;i+=4)
{
if(i%16 == 0)
fprintf(dump_fd,"\n0x%08x:\t",address+i);
fprintf(dump_fd,"0x%08x\t",*(A_UINT32*)(buffer+i));
}
}
else
{
fwrite(buffer,1 , length, dump_fd);
}
remaining -= length;
address += length;
}
}
fclose(dump_fd);
free(buffer);
} else {
usage();
}
break;
case DIAG_WRITE_TARGET:
if (!(flag & ADDRESS_FLAG))
{
usage(); /* no address specified */
}
if (!(flag & (FILE_FLAG | PARAM_FLAG)))
{
usage(); /* no data specified */
}
if ((flag & FILE_FLAG) && (flag & PARAM_FLAG))
{
usage(); /* too much data specified */
}
if (flag & FILE_FLAG)
{
struct stat filestat;
unsigned int file_length;
if ((fd = open(filename, O_RDONLY)) < 0)
{
fprintf(stderr, "err %s Could not open file"
" (%s)\n", __FUNCTION__, filename);
exit(1);
}
memset(&filestat, '\0', sizeof(struct stat));
buffer = (A_UINT8 *)MALLOC(MAX_BUF);
fstat(fd, &filestat);
file_length = filestat.st_size;
if (file_length == 0) {
fprintf(stderr, "err %s Zero length input file"
" (%s)\n", __FUNCTION__, filename);
exit(1);
}
if (flag & LENGTH_FLAG) {
if (length > file_length) {
fprintf(stderr, "err %s file %s: length (%d)"
" too short (%d)\n", __FUNCTION__,
filename, file_length, length);
exit(1);
}
} else {
length = file_length;
}
nqprintf(
"DIAG Write Target (address: 0x%x, filename: %s,"
" length: %d)\n", address, filename, length);
}
else
{ /* PARAM_FLAG */
nqprintf(
"DIAG Write Word (address: 0x%x, value: 0x%x)\n",
address, param);
length = sizeof(param);
buffer = (A_UINT8 *)&param;
fd = -1;
}
/*
* Write length bytes of data to memory/OTP.
* Data is either present in buffer OR
* needs to be read from fd in MAX_BUF chunks.
*
* Within the kernel, the implementation of
* DIAG_WRITE_TARGET further limits the size
* of each transfer over the interconnect.
*/
{
unsigned int remaining;
unsigned int otp_check_address = address;
if (flag & OTP_FLAG) {
/* Validate OTP write before committing anything */
remaining = length;
while (remaining)
{
int nbyte;
length = (remaining > MAX_BUF) ? MAX_BUF : remaining;
if (fd > 0)
{
nbyte = read(fd, buffer, length);
if (nbyte != (int)length) {
fprintf(stderr, "err %s read from file failed"
" (%d)\n", __FUNCTION__, nbyte);
exit(1);
}
}
if ((flag & OTP_FLAG) && !ValidWriteOTP(dev,
otp_check_address, buffer, length))
{
exit(1);
}
remaining -= length;
otp_check_address += length;
}
(void)lseek(fd, 0, SEEK_SET);
}
remaining = length;
while (remaining)
{
int nbyte;
length = (remaining > MAX_BUF) ? MAX_BUF : remaining;
if (fd > 0)
{
nbyte = read(fd, buffer, length);
if (nbyte != (int)length) {
fprintf(stderr, "err %s read from file failed"
" (%d)\n", __FUNCTION__, nbyte);
exit(1);
}
}
if (flag & OTP_FLAG) {
WriteTargetOTP(dev, address, buffer, length);
} else {
WriteTargetRange(dev, address, buffer, length);
}
remaining -= length;
address += length;
}
}
if (flag & FILE_FLAG) {
free(buffer);
close(fd);
}
break;
case DIAG_READ_WORD:
if ((flag & (ADDRESS_FLAG)) == (ADDRESS_FLAG))
{
nqprintf("DIAG Read Word (address: 0x%x)\n", address);
ReadTargetWord(dev, address, &param);
if (quiet()) {
printf("0x%x\n", param);
} else {
printf("Value in target at 0x%x: 0x%x (%d)\n",
address, param, param);
}
}
else usage();
break;
case DIAG_WRITE_WORD:
if ((flag & (ADDRESS_FLAG | PARAM_FLAG)) == (ADDRESS_FLAG | PARAM_FLAG))
{
A_UINT32 origvalue = 0;
if (flag & BITWISE_OP_FLAG) {
/* first read */
ReadTargetWord(dev, address, &origvalue);
param = origvalue;
/* now modify */
if (flag & AND_OP_FLAG) {
param &= bitwise_mask;
} else {
param |= bitwise_mask;
}
/* fall through to write out the parameter */
}
if (flag & BITWISE_OP_FLAG) {
if (quiet()) {
printf("0x%x\n", origvalue);
} else {
printf("DIAG Bit-Wise (%s) modify Word (address: 0x%x,"
" orig:0x%x, new: 0x%x, mask:0x%X)\n",
(flag & AND_OP_FLAG) ? "AND" : "OR", address,
origvalue, param, bitwise_mask );
}
} else{
nqprintf("DIAG Write Word (address: 0x%x, param:"
" 0x%x)\n", address, param);
}
WriteTargetWord(dev, address, param);
}
else usage();
break;
default:
usage();
}
exit (0);
}