diff options
Diffstat (limited to 'drivers/block/mg_disk.c')
-rw-r--r-- | drivers/block/mg_disk.c | 582 |
1 files changed, 582 insertions, 0 deletions
diff --git a/drivers/block/mg_disk.c b/drivers/block/mg_disk.c new file mode 100644 index 0000000..b74307a --- /dev/null +++ b/drivers/block/mg_disk.c @@ -0,0 +1,582 @@ +/* + * (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; +} |