blob: 420e472e78c5882cb45960fba51f911d68d951cb [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 file contains glue for Atheros ath spi flash interface
* Primitives are ath_spi_*
* mtd flash implements are ath_flash_*
*/
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/types.h>
#include <linux/errno.h>
#include <linux/slab.h>
#include <linux/mtd/mtd.h>
#include <linux/mtd/partitions.h>
#include <asm/delay.h>
#include <asm/io.h>
#include <asm/div64.h>
#include <atheros.h>
#include "ath_flash.h"
/* Legacy flash driver only supports 16M */
#define LEGACY_SPI_ADDR_BOUNDARY 0x1000000
static void ath_spi_wrear(uint32_t data);
static u_char ath_spi_rdear(void);
static int write_buff_ext(struct mtd_info *info, u_char *src,
loff_t offset, size_t cnt, size_t *retlen);
static int read_buff_ext(struct mtd_info *info, u_char *buf,
loff_t offset, size_t cnt, size_t *retlen);
/* this is passed in as a boot parameter by bootloader */
extern int __ath_flash_size;
/*
* statics
*/
static void ath_spi_write_enable(void);
static void ath_spi_poll(void);
#if !defined(ATH_SST_FLASH)
static void ath_spi_write_page(uint32_t addr, uint8_t * data, int len);
#endif
static void ath_spi_sector_erase(uint32_t addr);
static const char *part_probes[] __initdata = { "cmdlinepart", "RedBoot", NULL };
#define ATH_FLASH_SIZE_2MB (2*1024*1024)
#define ATH_FLASH_SIZE_4MB (4*1024*1024)
#define ATH_FLASH_SIZE_8MB (8*1024*1024)
#define ATH_FLASH_SIZE_32MB (32*1024*1024)
#define ATH_FLASH_SECTOR_SIZE_64KB (64*1024)
#define ATH_FLASH_PG_SIZE_256B 256
#define ATH_FLASH_NAME "ath-nor0"
/*
* bank geometry
*/
typedef struct ath_flash_geom {
uint32_t size;
uint32_t sector_size;
uint32_t nsectors;
uint32_t pgsize;
} ath_flash_geom_t;
ath_flash_geom_t flash_geom_tbl[ATH_FLASH_MAX_BANKS] = {
{
.size = ATH_FLASH_SIZE_32MB,
.sector_size = ATH_FLASH_SECTOR_SIZE_64KB,
.pgsize = ATH_FLASH_PG_SIZE_256B
}
};
static int
ath_flash_probe(void)
{
return 0;
}
#if defined(ATH_SST_FLASH)
void
ath_spi_flash_unblock(void)
{
ath_spi_write_enable();
ath_spi_bit_banger(ATH_SPI_CMD_WRITE_SR);
ath_spi_bit_banger(0x0);
ath_spi_go();
ath_spi_poll();
}
#endif
static int
ath_flash_erase(struct mtd_info *mtd, struct erase_info *instr)
{
int nsect, s_curr, s_last;
uint64_t res;
if (instr->addr + instr->len > mtd->size)
return (-EINVAL);
ath_mn_spi_enable_flash_cs();
res = instr->len;
do_div(res, mtd->erasesize);
nsect = res;
if (((uint32_t)instr->len) % mtd->erasesize)
nsect ++;
res = instr->addr;
do_div(res,mtd->erasesize);
s_curr = res;
s_last = s_curr + nsect;
do {
ath_spi_sector_erase(s_curr * ATH_SPI_SECTOR_SIZE);
} while (++s_curr < s_last);
ath_spi_done();
ath_mn_spi_disable_cs();
if (instr->callback) {
instr->state |= MTD_ERASE_DONE;
instr->callback(instr);
}
return 0;
}
static int
read_buff_under16m(struct mtd_info *mtd, loff_t from, size_t len,
size_t *retlen, u_char *buf)
{
size_t i;
if (len == 0) {
return 0;
}
if (from + len > mtd->size) {
return (-EINVAL);
}
ath_mn_spi_enable_flash_cs();
ath_spi_write_enable();
ath_spi_bit_banger(ATH_SPI_CMD_READ);
ath_spi_send_addr((uint32_t) from);
for (i = 0; i < len; ++i) {
ath_spi_delay_8();
buf[i] = (u_char) ath_reg_rd(ATH_SPI_RD_STATUS);
}
ath_spi_go();
ath_spi_done();
ath_mn_spi_disable_cs();
*retlen = len;
return 0;
}
#if defined(ATH_SST_FLASH)
static int
write_buff_under16m(struct mtd_info *mtd, loff_t dst, size_t len,
size_t * retlen, const u_char * src)
{
uint32_t val;
//printk("write len: %lu dst: 0x%x src: %p\n", len, dst, src);
*retlen = len;
for (; len; len--, dst++, src++) {
ath_spi_write_enable(); // dont move this above 'for'
ath_spi_bit_banger(ATH_SPI_CMD_PAGE_PROG);
ath_spi_send_addr(dst);
val = *src & 0xff;
ath_spi_bit_banger(val);
// Back ported bug fix, always write CE_LOW, CS_DIS before poll.
ath_spi_go();
ath_spi_poll();
}
/*
* Disable the Function Select
* Without this we can't re-read the written data
*/
ath_reg_wr(ATH_SPI_FS, 0);
if (len) {
*retlen -= len;
return -EIO;
}
return 0;
}
#else
static int
write_buff_under16m(struct mtd_info *mtd, loff_t to, size_t len,
size_t *retlen, const u_char *buf)
{
int total = 0, len_this_lp, bytes_this_page;
uint32_t addr = 0;
u_char *mem;
ath_mn_spi_enable_flash_cs();
while (total < len) {
mem = (u_char *) (buf + total);
addr = to + total;
bytes_this_page =
ATH_SPI_PAGE_SIZE - (addr % ATH_SPI_PAGE_SIZE);
len_this_lp = min(((int)len - total), bytes_this_page);
ath_spi_write_page(addr, mem, len_this_lp);
total += len_this_lp;
}
ath_spi_done();
ath_mn_spi_disable_cs();
*retlen = len;
return 0;
}
#endif
#ifdef ATH_SST_FLASH
static void
ath_spi_flash_chip_erase(void)
{
ath_spi_write_enable();
ath_spi_bit_banger(ATH_SPI_CMD_CHIP_ERASE);
ath_spi_go();
ath_spi_poll();
}
static int
ath_write_buff(flash_info_t *info, uchar *src, ulong dst, ulong len)
{
uint32_t val;
for (; len; len--, dst++, src++) {
ath_spi_write_enable(); // dont move this above 'for'
ath_spi_bit_banger(ATH_SPI_CMD_PAGE_PROG);
ath_spi_send_addr(dst);
val = *src & 0xff;
ath_spi_bit_banger(val);
ath_spi_go();
ath_spi_poll();
}
/*
* Disable the Function Select
* Without this we can't read from the chip again
*/
ath_reg_wr(ATH_SPI_FS, 0);
if (len) {
// how to differentiate errors ??
return ERR_PROG_ERROR;
} else {
return ERR_OK;
}
}
#else
static int
ath_write_buff(struct mtd_info *info, u_char *source, loff_t addr, size_t len)
{
int total = 0, len_this_lp, bytes_this_page;
ulong dst;
u_char *src;
while (total < len) {
src = source + total;
dst = addr + total;
bytes_this_page = ATH_SPI_PAGE_SIZE - (addr % ATH_SPI_PAGE_SIZE);
len_this_lp = ((len - total) > bytes_this_page) ?
bytes_this_page : (len - total);
ath_spi_write_page(dst, src, len_this_lp);
total += len_this_lp;
}
ath_spi_done();
return 0;
}
#endif
static int ath_flash_read(struct mtd_info *mtd, loff_t from, size_t len,
size_t *retlen, u_char *buf)
{
if (from >= LEGACY_SPI_ADDR_BOUNDARY) {
return read_buff_ext (mtd, buf, from, len, retlen);
} else {
return read_buff_under16m(mtd, from, len, retlen, buf);
}
}
static int ath_flash_write(struct mtd_info *mtd, loff_t dst, size_t len,
size_t * retlen, const u_char * src) {
if (dst >= LEGACY_SPI_ADDR_BOUNDARY) {
return write_buff_ext (mtd, src, dst, len, retlen);
} else {
return write_buff_under16m(mtd, dst, len, retlen, src);
}
}
/*
* sets up flash_info and returns size of FLASH (bytes)
*/
static int __init ath_flash_init(void)
{
int i, np;
ath_flash_geom_t *geom;
struct mtd_info *mtd;
struct mtd_partition *mtd_parts;
uint8_t index;
#if !(defined(CONFIG_MACH_AR934x) || defined(CONFIG_MACH_QCA955x) || defined(CONFIG_MACH_QCA953x) || defined(CONFIG_MACH_QCA956x))
#if defined(ATH_SST_FLASH)
ath_reg_wr_nf(ATH_SPI_CLOCK, 0x3);
ath_spi_flash_unblock();
ath_reg_wr(ATH_SPI_FS, 0);
#else
ath_reg_wr_nf(ATH_SPI_CLOCK, 0x43);
#endif
#endif
for (i = 0; i < ATH_FLASH_MAX_BANKS; i++) {
index = ath_flash_probe();
geom = &flash_geom_tbl[index];
/* set flash size to value from bootloader if it passed valid value */
/* otherwise use the default 4MB. */
if (__ath_flash_size >= 4 && __ath_flash_size <= 16)
geom->size = __ath_flash_size * 1024 * 1024;
mtd = kmalloc(sizeof(struct mtd_info), GFP_KERNEL);
if (!mtd) {
printk("Cant allocate mtd stuff\n");
return -1;
}
memset(mtd, 0, sizeof(struct mtd_info));
mtd->name = ATH_FLASH_NAME;
mtd->type = MTD_NORFLASH;
mtd->flags = MTD_CAP_NORFLASH | MTD_WRITEABLE;
mtd->size = geom->size;
mtd->erasesize = geom->sector_size;
mtd->numeraseregions = 0;
mtd->eraseregions = NULL;
mtd->owner = THIS_MODULE;
mtd->erase = ath_flash_erase;
mtd->read = ath_flash_read;
mtd->write = ath_flash_write;
mtd->writesize = 1;
np = parse_mtd_partitions(mtd, part_probes, &mtd_parts, 0);
if (np > 0) {
add_mtd_partitions(mtd, mtd_parts, np);
} else {
printk("No partitions found on flash bank %d\n", i);
}
}
/* Sanity check for module load order. */
printk(KERN_NOTICE "ath_flash driver initialized.");
return 0;
}
static void __exit ath_flash_exit(void)
{
/*
* nothing to do
*/
}
/*
* Primitives to implement flash operations
*/
static void
ath_spi_write_enable()
{
ath_reg_wr_nf(ATH_SPI_FS, 1);
ath_reg_wr_nf(ATH_SPI_WRITE, ATH_SPI_CS_DIS);
ath_spi_bit_banger(ATH_SPI_CMD_WREN);
ath_spi_go();
}
static void
ath_spi_poll()
{
int rd;
do {
ath_reg_wr_nf(ATH_SPI_WRITE, ATH_SPI_CS_DIS);
ath_spi_bit_banger(ATH_SPI_CMD_RD_STATUS);
ath_spi_delay_8();
ath_spi_go();
rd = (ath_reg_rd(ATH_SPI_RD_STATUS) & 1);
} while (rd);
}
static void
ath_spi_write_page(uint32_t addr, uint8_t *data, int len)
{
int i;
uint8_t ch;
ath_spi_write_enable();
ath_spi_bit_banger(ATH_SPI_CMD_PAGE_PROG);
ath_spi_send_addr(addr);
for (i = 0; i < len; i++) {
ch = *(data + i);
ath_spi_bit_banger(ch);
}
ath_spi_go();
ath_spi_poll();
}
static void
ath_spi_sector_erase(uint32_t addr)
{
uint32_t ori_ear = (uint32_t) ath_spi_rdear();
uint32_t new_ear = addr >> 24;
if (new_ear != ori_ear)
ath_spi_wrear(new_ear);
ath_spi_write_enable();
ath_spi_bit_banger(ATH_SPI_CMD_SECTOR_ERASE);
ath_spi_send_addr(addr);
ath_spi_go();
#if 0
/*
* Do not touch the GPIO's unnecessarily. Might conflict
* with customer's settings.
*/
display(0x7d);
#endif
ath_spi_poll();
/* recover extended address register */
if (new_ear != ori_ear)
ath_spi_wrear(ori_ear);
}
/* 32M extension */
static void ath_spi_wrear(uint32_t data)
{
ath_spi_write_enable();
ath_spi_bit_banger(ATH_SPI_CMD_WREAR);
ath_spi_bit_banger((u_char)data);
ath_spi_go();
ath_spi_poll();
}
static u_char ath_spi_rdear(void)
{
u_char data;
ath_spi_write_enable();
ath_spi_bit_banger(ATH_SPI_CMD_RDEAR);
ath_spi_delay_8();
ath_spi_go();
data = (u_char)(ath_reg_rd(ATH_SPI_RD_STATUS));
ath_spi_poll();
return(data);
}
static int
read_buff_ext(struct mtd_info *info, u_char *buf, loff_t offset, size_t len, size_t *retlen)
{
size_t i = 0;
uint32_t curr_addr = offset;
uint32_t ori_ear = (uint32_t)ath_spi_rdear();
uint32_t new_ear;
while (i < len) {
new_ear = curr_addr >> 24;
ath_spi_wrear(new_ear);
ath_spi_write_enable();
ath_spi_bit_banger(ATH_SPI_CMD_READ);
ath_spi_send_addr(curr_addr);
do{
ath_spi_delay_8();
*(buf + i++) = (u_char)(ath_reg_rd(ATH_SPI_RD_STATUS));
/* update the extended adress update if it's a multiple of 16M */
if(!((++ curr_addr) & (LEGACY_SPI_ADDR_BOUNDARY - 1))) {
break;
}
} while(i < len);
ath_spi_go();
}
if (new_ear != ori_ear) {
ath_spi_wrear(ori_ear);
}
ath_spi_done();
*retlen = i;
return 0;
}
static int
write_buff_ext(struct mtd_info *info, u_char *source, loff_t offset, size_t len, size_t *retlen)
{
int status;
uint32_t ori_ear = (uint32_t)ath_spi_rdear();
uint32_t new_ear = 0;
uint32_t curr_addr = offset;
uint32_t bytes_this_16M, total = 0;
*retlen = len;
while (len) {
size_t tmp;
new_ear = curr_addr >> 24;
ath_spi_wrear(new_ear);
bytes_this_16M = LEGACY_SPI_ADDR_BOUNDARY - curr_addr % LEGACY_SPI_ADDR_BOUNDARY;
bytes_this_16M = (bytes_this_16M < len) ? bytes_this_16M : len;
if((status = ath_write_buff(info, source + total, curr_addr, bytes_this_16M))
!= 0) {
printk(KERN_CRIT"failed to write 0x%x bytes to 0x%x\n", bytes_this_16M,
curr_addr);
break;
}
curr_addr += bytes_this_16M;
total += bytes_this_16M;
len -= bytes_this_16M;
}
if (new_ear != ori_ear) {
ath_spi_wrear(ori_ear);
}
ath_spi_done();
*retlen -= len;
return(status);
}
module_init(ath_flash_init);
module_exit(ath_flash_exit);