u-boot/drivers/block/mg_disk.c
unsik Kim 75eb82ec7c mflash: Initial mflash support
Mflash is fusion memory device mainly targeted consumer eletronic and
mobile phone.
Internally, it have nand flash and other hardware logics and supports
some different operation (ATA, IO, XIP) modes.

IO mode is custom mode for the host that doesn't have IDE interface.
(Many mobile targeted SoC doesn't have IDE bus)

This driver support mflash IO mode.

Followings are brief descriptions about IO mode.

1. IO mode based on ATA protocol and uses some custom command. (read
   confirm, write confirm)
2. IO mode uses SRAM bus interface.

Signed-off-by: unsik Kim <donari75@gmail.com>
2009-04-03 23:47:06 +02:00

583 lines
14 KiB
C

/*
* (C) Copyright 2009 mGine co.
* unsik Kim <donari75@gmail.com>
*
* See file CREDITS for list of people who contributed to this
* project.
*
* 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 <common.h>
#include <malloc.h>
#include <part.h>
#include <ata.h>
#include <asm/io.h>
#include "mg_disk_prv.h"
#ifndef CONFIG_MG_DISK_RES
#define CONFIG_MG_DISK_RES 0
#endif
#define MG_RES_SEC ((CONFIG_MG_DISK_RES) << 1)
static struct mg_host host;
static inline u32 mg_base(void)
{
return host.drv_data->base;
}
static block_dev_desc_t mg_disk_dev = {
.if_type = IF_TYPE_ATAPI,
.part_type = PART_TYPE_UNKNOWN,
.type = DEV_TYPE_HARDDISK,
.blksz = MG_SECTOR_SIZE,
.priv = &host };
static void mg_dump_status (const char *msg, unsigned int stat, unsigned err)
{
char *name = MG_DEV_NAME;
printf("%s: %s: status=0x%02x { ", name, msg, stat & 0xff);
if (stat & MG_REG_STATUS_BIT_BUSY)
printf("Busy ");
if (stat & MG_REG_STATUS_BIT_READY)
printf("DriveReady ");
if (stat & MG_REG_STATUS_BIT_WRITE_FAULT)
printf("WriteFault ");
if (stat & MG_REG_STATUS_BIT_SEEK_DONE)
printf("SeekComplete ");
if (stat & MG_REG_STATUS_BIT_DATA_REQ)
printf("DataRequest ");
if (stat & MG_REG_STATUS_BIT_CORRECTED_ERROR)
printf("CorrectedError ");
if (stat & MG_REG_STATUS_BIT_ERROR)
printf("Error ");
printf("}\n");
if ((stat & MG_REG_STATUS_BIT_ERROR)) {
printf("%s: %s: error=0x%02x { ", name, msg, err & 0xff);
if (err & MG_REG_ERR_BBK)
printf("BadSector ");
if (err & MG_REG_ERR_UNC)
printf("UncorrectableError ");
if (err & MG_REG_ERR_IDNF)
printf("SectorIdNotFound ");
if (err & MG_REG_ERR_ABRT)
printf("DriveStatusError ");
if (err & MG_REG_ERR_AMNF)
printf("AddrMarkNotFound ");
printf("}\n");
}
}
static unsigned int mg_wait (u32 expect, u32 msec)
{
u8 status;
u32 from, cur, err;
err = MG_ERR_NONE;
reset_timer();
from = get_timer(0);
status = readb(mg_base() + MG_REG_STATUS);
do {
cur = get_timer(from);
if (status & MG_REG_STATUS_BIT_BUSY) {
if (expect == MG_REG_STATUS_BIT_BUSY)
break;
} else {
/* Check the error condition! */
if (status & MG_REG_STATUS_BIT_ERROR) {
err = readb(mg_base() + MG_REG_ERROR);
mg_dump_status("mg_wait", status, err);
break;
}
if (expect == MG_STAT_READY)
if (MG_READY_OK(status))
break;
if (expect == MG_REG_STATUS_BIT_DATA_REQ)
if (status & MG_REG_STATUS_BIT_DATA_REQ)
break;
}
status = readb(mg_base() + MG_REG_STATUS);
} while (cur < msec);
if (cur >= msec)
err = MG_ERR_TIMEOUT;
return err;
}
static int mg_get_disk_id (void)
{
u16 id[(MG_SECTOR_SIZE / sizeof(u16))];
hd_driveid_t *iop = (hd_driveid_t *)id;
u32 i, err, res;
writeb(MG_CMD_ID, mg_base() + MG_REG_COMMAND);
err = mg_wait(MG_REG_STATUS_BIT_DATA_REQ, 3000);
if (err)
return err;
for(i = 0; i < (MG_SECTOR_SIZE / sizeof(u16)); i++)
id[i] = readw(mg_base() + MG_BUFF_OFFSET + i * 2);
writeb(MG_CMD_RD_CONF, mg_base() + MG_REG_COMMAND);
err = mg_wait(MG_STAT_READY, 3000);
if (err)
return err;
ata_swap_buf_le16(id, MG_SECTOR_SIZE / sizeof(u16));
if((iop->field_valid & 1) == 0)
return MG_ERR_TRANSLATION;
ata_id_c_string(id, (unsigned char *)mg_disk_dev.revision,
ATA_ID_FW_REV, sizeof(mg_disk_dev.revision));
ata_id_c_string(id, (unsigned char *)mg_disk_dev.vendor,
ATA_ID_PROD, sizeof(mg_disk_dev.vendor));
ata_id_c_string(id, (unsigned char *)mg_disk_dev.product,
ATA_ID_SERNO, sizeof(mg_disk_dev.product));
#ifdef __BIG_ENDIAN
iop->lba_capacity = (iop->lba_capacity << 16) |
(iop->lba_capacity >> 16);
#endif /* __BIG_ENDIAN */
if (MG_RES_SEC) {
MG_DBG("MG_RES_SEC=%d\n", MG_RES_SEC);
iop->cyls = (iop->lba_capacity - MG_RES_SEC) /
iop->sectors / iop->heads;
res = iop->lba_capacity -
iop->cyls * iop->heads * iop->sectors;
iop->lba_capacity -= res;
printf("mg_disk: %d sectors reserved\n", res);
}
mg_disk_dev.lba = iop->lba_capacity;
return MG_ERR_NONE;
}
static int mg_disk_reset (void)
{
struct mg_drv_data *prv_data = host.drv_data;
s32 err;
u8 init_status;
/* hdd rst low */
prv_data->mg_hdrst_pin(0);
err = mg_wait(MG_REG_STATUS_BIT_BUSY, 300);
if(err)
return err;
/* hdd rst high */
prv_data->mg_hdrst_pin(1);
err = mg_wait(MG_STAT_READY, 3000);
if(err)
return err;
/* soft reset on */
writeb(MG_REG_CTRL_RESET | MG_REG_CTRL_INTR_DISABLE,
mg_base() + MG_REG_DRV_CTRL);
err = mg_wait(MG_REG_STATUS_BIT_BUSY, 3000);
if(err)
return err;
/* soft reset off */
writeb(MG_REG_CTRL_INTR_DISABLE, mg_base() + MG_REG_DRV_CTRL);
err = mg_wait(MG_STAT_READY, 3000);
if(err)
return err;
init_status = readb(mg_base() + MG_REG_STATUS) & 0xf;
if (init_status == 0xf)
return MG_ERR_INIT_STAT;
return err;
}
static unsigned int mg_out(unsigned int sect_num,
unsigned int sect_cnt,
unsigned int cmd)
{
u32 err = MG_ERR_NONE;
err = mg_wait(MG_STAT_READY, 3000);
if (err)
return err;
writeb((u8)sect_cnt, mg_base() + MG_REG_SECT_CNT);
writeb((u8)sect_num, mg_base() + MG_REG_SECT_NUM);
writeb((u8)(sect_num >> 8), mg_base() + MG_REG_CYL_LOW);
writeb((u8)(sect_num >> 16), mg_base() + MG_REG_CYL_HIGH);
writeb((u8)((sect_num >> 24) | MG_REG_HEAD_LBA_MODE),
mg_base() + MG_REG_DRV_HEAD);
writeb(cmd, mg_base() + MG_REG_COMMAND);
return err;
}
static unsigned int mg_do_read_sects(void *buff, u32 sect_num, u32 sect_cnt)
{
u32 i, j, err;
u8 *buff_ptr = buff;
union mg_uniwb uniwb;
err = mg_out(sect_num, sect_cnt, MG_CMD_RD);
if (err)
return err;
for (i = 0; i < sect_cnt; i++) {
err = mg_wait(MG_REG_STATUS_BIT_DATA_REQ, 3000);
if (err)
return err;
if ((u32)buff_ptr & 1) {
for (j = 0; j < MG_SECTOR_SIZE >> 1; j++) {
uniwb.w = readw(mg_base() + MG_BUFF_OFFSET
+ (j << 1));
*buff_ptr++ = uniwb.b[0];
*buff_ptr++ = uniwb.b[1];
}
} else {
for(j = 0; j < MG_SECTOR_SIZE >> 1; j++) {
*(u16 *)buff_ptr = readw(mg_base() +
MG_BUFF_OFFSET + (j << 1));
buff_ptr += 2;
}
}
writeb(MG_CMD_RD_CONF, mg_base() + MG_REG_COMMAND);
MG_DBG("%u (0x%8.8x) sector read", sect_num + i,
(sect_num + i) * MG_SECTOR_SIZE);
}
return err;
}
unsigned int mg_disk_read_sects(void *buff, u32 sect_num, u32 sect_cnt)
{
u32 quotient, residue, i, err;
u8 *buff_ptr = buff;
quotient = sect_cnt >> 8;
residue = sect_cnt % 256;
for (i = 0; i < quotient; i++) {
MG_DBG("sect num : %u buff : 0x%8.8x", sect_num, (u32)buff_ptr);
err = mg_do_read_sects(buff_ptr, sect_num, 256);
if (err)
return err;
sect_num += 256;
buff_ptr += 256 * MG_SECTOR_SIZE;
}
if (residue) {
MG_DBG("sect num : %u buff : %8.8x", sect_num, (u32)buff_ptr);
err = mg_do_read_sects(buff_ptr, sect_num, residue);
}
return err;
}
unsigned long mg_block_read (int dev, unsigned long start,
lbaint_t blkcnt, void *buffer)
{
start += MG_RES_SEC;
if (! mg_disk_read_sects(buffer, start, blkcnt))
return blkcnt;
else
return 0;
}
unsigned int mg_disk_read (u32 addr, u8 *buff, u32 len)
{
u8 *sect_buff, *buff_ptr = buff;
u32 cur_addr, next_sec_addr, end_addr, cnt, sect_num;
u32 err = MG_ERR_NONE;
/* TODO : sanity chk */
cnt = 0;
cur_addr = addr;
end_addr = addr + len;
sect_buff = malloc(MG_SECTOR_SIZE);
if (cur_addr & MG_SECTOR_SIZE_MASK) {
next_sec_addr = (cur_addr + MG_SECTOR_SIZE) &
~MG_SECTOR_SIZE_MASK;
sect_num = cur_addr >> MG_SECTOR_SIZE_SHIFT;
err = mg_disk_read_sects(sect_buff, sect_num, 1);
if (err)
goto mg_read_exit;
if (end_addr < next_sec_addr) {
memcpy(buff_ptr,
sect_buff + (cur_addr & MG_SECTOR_SIZE_MASK),
end_addr - cur_addr);
MG_DBG("copies %u byte from sector offset 0x%8.8x",
end_addr - cur_addr, cur_addr);
cur_addr = end_addr;
} else {
memcpy(buff_ptr,
sect_buff + (cur_addr & MG_SECTOR_SIZE_MASK),
next_sec_addr - cur_addr);
MG_DBG("copies %u byte from sector offset 0x%8.8x",
next_sec_addr - cur_addr, cur_addr);
buff_ptr += (next_sec_addr - cur_addr);
cur_addr = next_sec_addr;
}
}
if (cur_addr < end_addr) {
sect_num = cur_addr >> MG_SECTOR_SIZE_SHIFT;
cnt = ((end_addr & ~MG_SECTOR_SIZE_MASK) - cur_addr) >>
MG_SECTOR_SIZE_SHIFT;
if (cnt)
err = mg_disk_read_sects(buff_ptr, sect_num, cnt);
if (err)
goto mg_read_exit;
buff_ptr += cnt * MG_SECTOR_SIZE;
cur_addr += cnt * MG_SECTOR_SIZE;
if (cur_addr < end_addr) {
sect_num = cur_addr >> MG_SECTOR_SIZE_SHIFT;
err = mg_disk_read_sects(sect_buff, sect_num, 1);
if (err)
goto mg_read_exit;
memcpy(buff_ptr, sect_buff, end_addr - cur_addr);
MG_DBG("copies %u byte", end_addr - cur_addr);
}
}
mg_read_exit:
free(sect_buff);
return err;
}
static int mg_do_write_sects(void *buff, u32 sect_num, u32 sect_cnt)
{
u32 i, j, err;
u8 *buff_ptr = buff;
union mg_uniwb uniwb;
err = mg_out(sect_num, sect_cnt, MG_CMD_WR);
if (err)
return err;
for (i = 0; i < sect_cnt; i++) {
err = mg_wait(MG_REG_STATUS_BIT_DATA_REQ, 3000);
if (err)
return err;
if ((u32)buff_ptr & 1) {
uniwb.b[0] = *buff_ptr++;
uniwb.b[1] = *buff_ptr++;
writew(uniwb.w, mg_base() + MG_BUFF_OFFSET + (j << 1));
} else {
for(j = 0; j < MG_SECTOR_SIZE >> 1; j++) {
writew(*(u16 *)buff_ptr,
mg_base() + MG_BUFF_OFFSET +
(j << 1));
buff_ptr += 2;
}
}
writeb(MG_CMD_WR_CONF, mg_base() + MG_REG_COMMAND);
MG_DBG("%u (0x%8.8x) sector write",
sect_num + i, (sect_num + i) * MG_SECTOR_SIZE);
}
return err;
}
unsigned int mg_disk_write_sects(void *buff, u32 sect_num, u32 sect_cnt)
{
u32 quotient, residue, i;
u32 err = MG_ERR_NONE;
u8 *buff_ptr = buff;
quotient = sect_cnt >> 8;
residue = sect_cnt % 256;
for (i = 0; i < quotient; i++) {
MG_DBG("sect num : %u buff : %8.8x", sect_num, (u32)buff_ptr);
err = mg_do_write_sects(buff_ptr, sect_num, 256);
if (err)
return err;
sect_num += 256;
buff_ptr += 256 * MG_SECTOR_SIZE;
}
if (residue) {
MG_DBG("sect num : %u buff : %8.8x", sect_num, (u32)buff_ptr);
err = mg_do_write_sects(buff_ptr, sect_num, residue);
}
return err;
}
unsigned long mg_block_write (int dev, unsigned long start,
lbaint_t blkcnt, const void *buffer)
{
start += MG_RES_SEC;
if (!mg_disk_write_sects((void *)buffer, start, blkcnt))
return blkcnt;
else
return 0;
}
unsigned int mg_disk_write(u32 addr, u8 *buff, u32 len)
{
u8 *sect_buff, *buff_ptr = buff;
u32 cur_addr, next_sec_addr, end_addr, cnt, sect_num;
u32 err = MG_ERR_NONE;
/* TODO : sanity chk */
cnt = 0;
cur_addr = addr;
end_addr = addr + len;
sect_buff = malloc(MG_SECTOR_SIZE);
if (cur_addr & MG_SECTOR_SIZE_MASK) {
next_sec_addr = (cur_addr + MG_SECTOR_SIZE) &
~MG_SECTOR_SIZE_MASK;
sect_num = cur_addr >> MG_SECTOR_SIZE_SHIFT;
err = mg_disk_read_sects(sect_buff, sect_num, 1);
if (err)
goto mg_write_exit;
if (end_addr < next_sec_addr) {
memcpy(sect_buff + (cur_addr & MG_SECTOR_SIZE_MASK),
buff_ptr, end_addr - cur_addr);
MG_DBG("copies %u byte to sector offset 0x%8.8x",
end_addr - cur_addr, cur_addr);
cur_addr = end_addr;
} else {
memcpy(sect_buff + (cur_addr & MG_SECTOR_SIZE_MASK),
buff_ptr, next_sec_addr - cur_addr);
MG_DBG("copies %u byte to sector offset 0x%8.8x",
next_sec_addr - cur_addr, cur_addr);
buff_ptr += (next_sec_addr - cur_addr);
cur_addr = next_sec_addr;
}
err = mg_disk_write_sects(sect_buff, sect_num, 1);
if (err)
goto mg_write_exit;
}
if (cur_addr < end_addr) {
sect_num = cur_addr >> MG_SECTOR_SIZE_SHIFT;
cnt = ((end_addr & ~MG_SECTOR_SIZE_MASK) - cur_addr) >>
MG_SECTOR_SIZE_SHIFT;
if (cnt)
err = mg_disk_write_sects(buff_ptr, sect_num, cnt);
if (err)
goto mg_write_exit;
buff_ptr += cnt * MG_SECTOR_SIZE;
cur_addr += cnt * MG_SECTOR_SIZE;
if (cur_addr < end_addr) {
sect_num = cur_addr >> MG_SECTOR_SIZE_SHIFT;
err = mg_disk_read_sects(sect_buff, sect_num, 1);
if (err)
goto mg_write_exit;
memcpy(sect_buff, buff_ptr, end_addr - cur_addr);
MG_DBG("copies %u byte", end_addr - cur_addr);
err = mg_disk_write_sects(sect_buff, sect_num, 1);
}
}
mg_write_exit:
free(sect_buff);
return err;
}
block_dev_desc_t *mg_disk_get_dev(int dev)
{
return ((block_dev_desc_t *) & mg_disk_dev);
}
/* must override this function */
struct mg_drv_data * __attribute__((weak)) mg_get_drv_data (void)
{
puts ("### WARNING ### port mg_get_drv_data function\n");
return NULL;
}
unsigned int mg_disk_init (void)
{
struct mg_drv_data *prv_data;
u32 err = MG_ERR_NONE;
prv_data = mg_get_drv_data();
if (! prv_data) {
printf("%s:%d fail (no driver_data)\n", __func__, __LINE__);
err = MG_ERR_NO_DRV_DATA;
return err;
}
((struct mg_host *)mg_disk_dev.priv)->drv_data = prv_data;
/* init ctrl pin */
if (prv_data->mg_ctrl_pin_init)
prv_data->mg_ctrl_pin_init();
if (! prv_data->mg_hdrst_pin) {
err = MG_ERR_CTRL_RST;
return err;
}
/* disk reset */
err = mg_disk_reset();
if (err) {
printf("%s:%d fail (err code : %d)\n", __func__, __LINE__, err);
return err;
}
/* get disk id */
err = mg_get_disk_id();
if (err) {
printf("%s:%d fail (err code : %d)\n", __func__, __LINE__, err);
return err;
}
mg_disk_dev.block_read = mg_block_read;
mg_disk_dev.block_write = mg_block_write;
init_part(&mg_disk_dev);
dev_print(&mg_disk_dev);
return err;
}