/* * (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; #ifdef CONFIG_SYS_LOW_RES_TIMER reset_timer(); #endif 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; } #ifdef CONFIG_PARTITIONS block_dev_desc_t *mg_disk_get_dev(int dev) { return ((block_dev_desc_t *) & mg_disk_dev); } #endif /* 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; }