[U-Boot] [PATCH] Add support for kirkwood sata controller

Tor Krill tor at excito.com
Thu Jun 24 15:57:13 CEST 2010


A rudimentary sata driver for the Marvell Kirkwood CPU based on
the architecture of the fsl_sata driver.

Signed-off-by: Tor Krill <tor at excito.com>
---
 drivers/block/Makefile  |    1 +
 drivers/block/sata_mv.c |  800 +++++++++++++++++++++++++++++++++++++++++++++++
 drivers/block/sata_mv.h |  229 ++++++++++++++
 3 files changed, 1030 insertions(+), 0 deletions(-)
 create mode 100644 drivers/block/sata_mv.c
 create mode 100644 drivers/block/sata_mv.h

diff --git a/drivers/block/Makefile b/drivers/block/Makefile
index 3f6ad5c..5c1de8b 100644
--- a/drivers/block/Makefile
+++ b/drivers/block/Makefile
@@ -32,6 +32,7 @@ COBJS-$(CONFIG_IDE_SIL680) += sil680.o
 COBJS-$(CONFIG_LIBATA) += libata.o
 COBJS-$(CONFIG_PATA_BFIN) += pata_bfin.o
 COBJS-$(CONFIG_SATA_DWC) += sata_dwc.o
+COBJS-$(CONFIG_SATA_MV) += sata_mv.o
 COBJS-$(CONFIG_SATA_SIL3114) += sata_sil3114.o
 COBJS-$(CONFIG_SCSI_AHCI) += ahci.o
 COBJS-$(CONFIG_SCSI_SYM53C8XX) += sym53c8xx.o
diff --git a/drivers/block/sata_mv.c b/drivers/block/sata_mv.c
new file mode 100644
index 0000000..dfcae16
--- /dev/null
+++ b/drivers/block/sata_mv.c
@@ -0,0 +1,800 @@
+/*
+ * Copyright (C) Excito Elektronik i Skåne AB, 2010.
+ * Author: Tor Krill <tor at excito.com>
+ *
+ * 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
+ *
+ */
+
+/*
+ * TODO:
+ * Better error recovery
+ * No support for using PRDs (Thus max 64KB transfers)
+ * No NCQ support
+ * No port multiplier support
+ * No flush after write
+ */
+
+/*
+ * Should data structures be malloced?
+ */
+#define MALLOC_QUEUE 1
+
+#include <common.h>
+#include <asm/io.h>
+#include <malloc.h>
+#include <libata.h>
+#include <fis.h>
+#include "sata_mv.h"
+
+extern block_dev_desc_t sata_dev_desc[CONFIG_SYS_SATA_MAX_DEVICE];
+
+/* Keep track if hw is initialized or not */
+static u32 hw_init = 0;
+
+/* forwards */
+static int get_reqip(int port);
+static int get_reqop(int port);
+
+struct mv_priv{
+	char		name[12];
+	u32			link;
+	u32			regbase;
+	u32			queue_depth;
+	u16			pio;
+	u16			mwdma;
+	u16			udma;
+
+#ifdef MALLOC_QUEUE
+	void		*crqb_alloc;
+	struct crqb	*request;
+
+	void		*crpb_alloc;
+	struct crpb	*response;
+
+#else
+	struct crqb request[REQUEST_QUEUE_SIZE] __attribute__ ((aligned(CRQB_ALIGN)));
+	struct crpb response[REQUEST_QUEUE_SIZE] __attribute__ ((aligned(CRPB_ALIGN)));
+#endif
+
+};
+
+static inline void mdelay(unsigned long msec){
+	unsigned long i;
+	for (i = 0; i < msec; i++)
+		udelay(1000);
+}
+
+static int ata_wait_register(volatile unsigned *addr, u32 mask,
+                         u32 val, u32 timeout_msec){
+	int i;
+	u32 temp;
+
+	for (i = 0; (((temp = in_le32(addr)) & mask) != val)
+			&& i < timeout_msec; i++)
+				mdelay(1);
+	return (i < timeout_msec) ? 0 : -1;
+}
+
+
+/* Cut from sata_mv in linux kernel */
+static int mv_stop_edma_engine(int port){
+	int i;
+	struct mv_priv *priv;
+
+	priv = (struct mv_priv *) sata_dev_desc[port].priv;
+
+	/* Disable eDMA.  The disable bit auto clears. */
+	out_le32(priv->regbase + EDMA_CMD, EDMA_CMD_DISEDMA);
+
+	/* Wait for the chip to confirm eDMA is off. */
+	for (i = 10000; i > 0; i--) {
+		u32 reg = in_le32(priv->regbase + EDMA_CMD);
+		if (!(reg & EDMA_CMD_ENEDMA)){
+			debug("EDMA stop on port %d succesful\n\r",port);
+			return 0;
+		}
+		udelay(10);
+	}
+	debug("EDMA stop on port %d failed\n\r",port);
+	return -1;
+}
+
+static int mv_start_edma_engine(int port){
+	struct mv_priv *priv =
+			(struct mv_priv *) sata_dev_desc[port].priv;
+	u32 tmp;
+
+	/* Check preconditions */
+	tmp = in_le32(priv->regbase + SIR_SSTATUS);
+	if ((tmp & SSTATUS_DET_MASK) != 0x03) {
+		printf("Device error on port: %d\n\r",port);
+		return -1;
+	}
+
+	tmp = in_le32(priv->regbase+PIO_CMD_STATUS);
+	if ( tmp & ( ATA_BUSY | ATA_DRQ )){
+		printf("Device not ready on port: %d\n\r",port);
+		return -1;
+	}
+
+	/* Clear interrupt cause */
+	out_le32(priv->regbase+EDMA_IECR, 0x0);
+
+	tmp = in_le32( KW_SATAHC_BASE + SATAHC_ICR );
+	tmp &= ~( port==0 ? SATAHC_ICR_PORT0 : SATAHC_ICR_PORT1);
+	out_le32( KW_SATAHC_BASE + SATAHC_ICR ,tmp);
+
+	/* Configure edma operation */
+	tmp=in_le32(priv->regbase+EDMA_CFG);
+	tmp &= ~EDMA_CFG_NCQ; /* No NCQ */
+	tmp &= ~EDMA_CFG_EQUE; /* Dont queue operations */
+	out_le32(priv->regbase+EDMA_CFG,tmp);
+
+	out_le32(priv->regbase+SIR_FIS_IRQ_CAUSE, 0x0);
+
+	/* Configure fis, set all to no-wait for now */
+	out_le32(priv->regbase+SIR_FIS_CFG, 0x0);
+
+	/* Setup request queue */
+	out_le32(priv->regbase+EDMA_RQBA_HI, 0x0);
+#ifdef MALLOC_QUEUE
+	out_le32(priv->regbase+EDMA_RQIPR, priv->request);
+#else
+	out_le32(priv->regbase+EDMA_RQIPR, &priv->request);
+#endif
+	out_le32(priv->regbase+EDMA_RQOPR, 0x0);
+
+	/* Setup response queue */
+	out_le32(priv->regbase+EDMA_RSBA_HI, 0x0);
+#ifdef MALLOC_QUEUE
+	out_le32(priv->regbase+EDMA_RSOPR, priv->response);
+#else
+	out_le32(priv->regbase+EDMA_RSOPR, &priv->response);
+#endif
+	out_le32(priv->regbase+EDMA_RSIPR, 0x0);
+
+	/* Start edma */
+	out_le32(priv->regbase+EDMA_CMD, EDMA_CMD_ENEDMA);
+
+	return 0;
+}
+
+static int mv_reset_channel(int port){
+	struct mv_priv *priv;
+	priv = (struct mv_priv *) sata_dev_desc[port].priv;
+
+	/* Make sure edma is stopped  */
+	mv_stop_edma_engine(port);
+
+	out_le32(priv->regbase + EDMA_CMD, EDMA_CMD_ATARST);
+	udelay(25);     /* allow reset propagation */
+	out_le32(priv->regbase + EDMA_CMD, 0);
+	mdelay(150);
+	return 0;
+}
+
+static void mv_reset_port(int port){
+	struct mv_priv *priv;
+	priv = (struct mv_priv *) sata_dev_desc[port].priv;
+
+	mv_reset_channel(port);
+
+	out_le32(priv->regbase+EDMA_CMD, 0x0);
+	out_le32(priv->regbase+EDMA_CFG, 0x101f);
+	/* out_le32(priv->regbase+EDMA_TIMER, 0x0);  ???? */
+	out_le32(priv->regbase+EDMA_IECR, 0x0);
+	out_le32(priv->regbase+EDMA_IEMR, 0x0);
+	out_le32(priv->regbase+EDMA_RQBA_HI, 0x0);
+	out_le32(priv->regbase+EDMA_RQIPR, 0x0);
+	out_le32(priv->regbase+EDMA_RQOPR, 0x0);
+	out_le32(priv->regbase+EDMA_RSBA_HI, 0x0);
+	out_le32(priv->regbase+EDMA_RSIPR, 0x0);
+	out_le32(priv->regbase+EDMA_RSOPR, 0x0);
+	/* out_le32(priv->regbase+EDMA_TEST_CTL, 0x0); unknown register */
+
+	out_le32(priv->regbase+EDMA_IORTO,0xfa);
+
+}
+
+static void mv_reset_one_hc(void){
+	out_le32(KW_SATAHC_BASE+SATAHC_ICT, 0x00);
+	out_le32(KW_SATAHC_BASE+SATAHC_ITT, 0x00);
+	out_le32(KW_SATAHC_BASE+SATAHC_ICR, 0x00);
+}
+
+static int probe_port(int port){
+	u32 tmp;
+	int tries, tries2, set15=0;
+	struct mv_priv *priv;
+	priv = (struct mv_priv *) sata_dev_desc[port].priv;
+
+	debug("Probe port: %d\n\r",port);
+
+	for ( tries=0; tries<2; tries++){
+
+		/* Clear SError */
+		out_le32(priv->regbase+SIR_SERROR,0x0);
+
+		/* trigger com-init */
+		tmp = in_le32(priv->regbase + SIR_SCONTROL);
+		tmp = (tmp & 0x0f0) | 0x300 | SIR_SCONTROL_DETEN;
+		out_le32(priv->regbase + SIR_SCONTROL, tmp);
+
+		mdelay(1);
+
+		tmp = in_le32(priv->regbase + SIR_SCONTROL);
+		tries2 = 5;
+		do{
+			tmp = (tmp & 0x0f0) | 0x300;
+			out_le32(priv->regbase + SIR_SCONTROL, tmp);
+			mdelay(200);
+			tmp = in_le32(priv->regbase + SIR_SCONTROL);
+		} while ((tmp & 0xf0f) != 0x300 && tries2--);
+
+		mdelay(100);
+
+		for (tries2 = 0; tries2 < 200; tries2++) {
+
+			tmp = in_le32(priv->regbase + SIR_SSTATUS);
+			if ((tmp & SSTATUS_DET_MASK) == 0x03) {
+				debug("Found device on port\n\r");
+				return 0;
+			}
+			mdelay(1);
+		}
+
+		if ((tmp & SSTATUS_DET_MASK) == 0) {
+			debug("No device attached on port %d\n\r",port);
+			return -1;
+		}
+
+		if ( !set15 ){
+			/* Try on 1.5Gb/S */
+			debug("Try 1.5Gb link\n\r");
+			set15=1;
+			out_le32(priv->regbase+SIR_SCONTROL, 0x304);
+
+			tmp = in_le32(priv->regbase+SIR_ICFG);
+			tmp &= ~SIR_CFG_GEN2EN;
+			out_le32(priv->regbase+SIR_ICFG, tmp);
+
+			mv_reset_channel(port);
+		}
+
+	}
+
+	debug("Failed to probe port\n\r");
+	return -1;
+}
+
+/* Get request queue in pointer */
+static int get_reqip(int port){
+	u32 tmp;
+	struct mv_priv *priv = (struct mv_priv *) sata_dev_desc[port].priv;
+
+	tmp = in_le32(priv->regbase+EDMA_RQIPR) & EDMA_RQIPR_IPMASK;
+	tmp = tmp >> EDMA_RQIPR_IPSHIFT;
+
+	return tmp;
+}
+
+static void set_reqip(int port, int reqin){
+	struct mv_priv *priv = (struct mv_priv *) sata_dev_desc[port].priv;
+	u32 tmp;
+	tmp = in_le32(priv->regbase+EDMA_RQIPR) & ~EDMA_RQIPR_IPMASK;
+	tmp |= ((reqin << EDMA_RQIPR_IPSHIFT) & EDMA_RQIPR_IPMASK);
+	out_le32(priv->regbase+EDMA_RQIPR, tmp);
+}
+
+/* Get next available slot, ignoring possible overwrite */
+static int get_next_reqip(int port){
+	int slot=get_reqip(port);
+	slot = (slot + 1) % REQUEST_QUEUE_SIZE;
+	return slot;
+}
+
+/* Get request queue out pointer */
+static int get_reqop(int port){
+	u32 tmp;
+	struct mv_priv *priv = (struct mv_priv *) sata_dev_desc[port].priv;
+
+	tmp = in_le32(priv->regbase+EDMA_RQOPR) & EDMA_RQOPR_OPMASK;
+	tmp = tmp >> EDMA_RQOPR_OPSHIFT;
+
+	return tmp;
+}
+
+/* Get response queue in pointer */
+static int get_rspip(int port){
+	u32 tmp;
+	struct mv_priv *priv = (struct mv_priv *) sata_dev_desc[port].priv;
+
+	tmp = in_le32(priv->regbase+EDMA_RSIPR) & EDMA_RSIPR_IPMASK;
+	tmp = tmp >> EDMA_RSIPR_IPSHIFT;
+
+	return tmp;
+}
+
+/* Get response queue out pointer */
+static int get_rspop(int port){
+	u32 tmp;
+	struct mv_priv *priv = (struct mv_priv *) sata_dev_desc[port].priv;
+
+	tmp = in_le32(priv->regbase+EDMA_RSOPR) & EDMA_RSOPR_OPMASK;
+	tmp = tmp >> EDMA_RSOPR_OPSHIFT;
+	return tmp;
+}
+/* Get next response queue pointer  */
+static int get_next_rspop(int port){
+	return (get_rspop(port) + 1) % RESPONSE_QUEUE_SIZE;
+}
+
+/* Set response queue pointer */
+static void set_rspop(int port, int reqin){
+	struct mv_priv *priv = (struct mv_priv *) sata_dev_desc[port].priv;
+	u32 tmp;
+
+	tmp = in_le32(priv->regbase+EDMA_RSOPR) & ~EDMA_RSOPR_OPMASK;
+	tmp |= ((reqin << EDMA_RSOPR_OPSHIFT) & EDMA_RSOPR_OPMASK);
+
+	out_le32(priv->regbase+EDMA_RSOPR, tmp);
+}
+
+
+static int wait_dma_completion(int port, int index, u32 timeout_msec){
+	u32 tmp,res;
+
+	tmp = port==0?SATAHC_ICR_PORT0:SATAHC_ICR_PORT1;
+
+	if ( (res = ata_wait_register(
+			(u32*)(KW_SATAHC_BASE+SATAHC_ICR), tmp ,tmp, timeout_msec))){
+		printf("Failed to wait for completion on port %d\n",port);
+	}
+
+	return res;
+}
+
+static void process_responses(int port){
+	struct mv_priv *priv = (struct mv_priv *) sata_dev_desc[port].priv;
+	u32 tmp;
+	u32 outind = get_rspop(port);
+
+	/* Ack interrupts */
+	tmp = in_le32(KW_SATAHC_BASE+SATAHC_ICR);
+	if ( port == 0 ){
+		tmp &= ~( 1<<0 | 1<<8);
+	}else{
+		tmp &= ~(1<<1 | 1<<9);
+	}
+	tmp &= ~(1<<4);
+	out_le32(KW_SATAHC_BASE+SATAHC_ICR,tmp);
+
+	while(get_rspip(port)!=outind){
+		debug("Response index %d flags %08x on port %d\n",outind,priv->response[outind].flags,port);
+		outind=get_next_rspop(port);
+		set_rspop(port,outind);
+	}
+}
+
+static int mv_ata_exec_ata_cmd(int port, struct sata_fis_h2d *cfis,
+				u8 *buffer, u32 len, u32 iswrite){
+	struct mv_priv *priv = (struct mv_priv *) sata_dev_desc[port].priv;
+
+	int slot;
+
+	if ( len >= 64*1024 ){
+		printf("We only support <64K transfers for now\n");
+		return -1;
+	}
+
+	/* Initialize request */
+
+	slot = get_reqip(port);
+
+	memset(&priv->request[slot], 0, sizeof(struct crqb));
+	priv->request[slot].dtb_low=(u32)buffer;
+	/* Dont use PRDs */
+	priv->request[slot].control_flags = CRQB_CNTRLFLAGS_PRDMODE;
+	priv->request[slot].control_flags |= iswrite?0:CRQB_CNTRLFLAGS_DIR;
+	priv->request[slot].control_flags |=
+			((cfis->pm_port_c<<CRQB_CNTRLFLAGS_PMPORTSHIFT)
+					& CRQB_CNTRLFLAGS_PMPORTMASK);
+
+	priv->request[slot].drb_count=len;
+
+	priv->request[slot].ata_cmd_feat =
+			((cfis->command<<CRQB_CMDFEAT_CMDSHIFT) &
+					CRQB_CMDFEAT_CMDMASK);
+	priv->request[slot].ata_cmd_feat |=
+			((cfis->features<<CRQB_CMDFEAT_FEATSHIFT) &
+					CRQB_CMDFEAT_FEATMASK);
+
+	priv->request[slot].ata_addr =
+			((cfis->lba_low<<CRQB_ADDR_LBA_LOWSHIFT) &
+					CRQB_ADDR_LBA_LOWMASK);
+	priv->request[slot].ata_addr |=
+			((cfis->lba_mid<<CRQB_ADDR_LBA_MIDSHIFT) &
+					CRQB_ADDR_LBA_MIDMASK);
+	priv->request[slot].ata_addr |=
+			((cfis->lba_high<<CRQB_ADDR_LBA_HIGHSHIFT) &
+					CRQB_ADDR_LBA_HIGHMASK);
+	priv->request[slot].ata_addr |=
+			((cfis->device<<CRQB_ADDR_DEVICE_SHIFT) &
+					CRQB_ADDR_DEVICE_MASK);
+
+	priv->request[slot].ata_addr_exp =
+			((cfis->lba_low_exp<<CRQB_ADDR_LBA_LOW_EXP_SHIFT) &
+					CRQB_ADDR_LBA_LOW_EXP_MASK);
+	priv->request[slot].ata_addr_exp |=
+			((cfis->lba_mid_exp<<CRQB_ADDR_LBA_MID_EXP_SHIFT) &
+					CRQB_ADDR_LBA_MID_EXP_MASK);
+	priv->request[slot].ata_addr_exp |=
+			((cfis->lba_high_exp<<CRQB_ADDR_LBA_HIGH_EXP_SHIFT) &
+					CRQB_ADDR_LBA_HIGH_EXP_MASK);
+	priv->request[slot].ata_addr_exp |=
+			((cfis->features_exp<<CRQB_ADDR_FEATURE_EXP__SHIFT) &
+					CRQB_ADDR_FEATURE_EXP__MASK);
+
+	priv->request[slot].ata_sect_count =
+			((cfis->sector_count<<CRQB_SECTCOUNT_COUNT_SHIFT) &
+					CRQB_SECTCOUNT_COUNT_MASK);
+	priv->request[slot].ata_sect_count |=
+			((cfis->sector_count_exp<<CRQB_SECTCOUNT_COUNT_EXP_SHIFT) &
+					CRQB_SECTCOUNT_COUNT_EXP_MASK);
+
+	/* Trigger operation */
+	slot=get_next_reqip(port);
+	set_reqip(port, slot);
+
+	/* Wait for completion */
+	if( wait_dma_completion(port,slot,10000) ){
+		printf("ATA operation timed out\n");
+		return -1;
+	}
+
+	process_responses(port);
+
+	return len;
+}
+
+static u32 mv_sata_rw_cmd_ext(int port, u32 start, u32 blkcnt,
+		u8 *buffer, int is_write){
+
+	struct sata_fis_h2d cfis;
+	u32 res;
+	u64 block;
+
+	block = (u64)start;
+
+	memset(&cfis, 0, sizeof(struct sata_fis_h2d));
+
+	cfis.fis_type = SATA_FIS_TYPE_REGISTER_H2D;
+
+	cfis.command = (is_write) ? ATA_CMD_WRITE_EXT
+				 : ATA_CMD_READ_EXT;
+
+	cfis.lba_high_exp = (block >> 40) & 0xff;
+	cfis.lba_mid_exp = (block >> 32) & 0xff;
+	cfis.lba_low_exp = (block >> 24) & 0xff;
+	cfis.lba_high = (block >> 16) & 0xff;
+	cfis.lba_mid = (block >> 8) & 0xff;
+	cfis.lba_low = block & 0xff;
+	cfis.device = ATA_LBA;
+	cfis.sector_count_exp = (blkcnt >> 8) & 0xff;
+	cfis.sector_count = blkcnt & 0xff;
+
+	res = mv_ata_exec_ata_cmd(
+			port,&cfis,buffer,ATA_SECT_SIZE * blkcnt, is_write);
+
+	return res>=0?blkcnt:res;
+}
+
+
+static u32 mv_sata_rw_cmd(int port, u32 start, u32 blkcnt, u8 *buffer
+		, int is_write){
+
+	struct sata_fis_h2d cfis;
+	u32 block, res;
+	u8 cmd;
+
+	block = start;
+
+	memset(&cfis, 0, sizeof(struct sata_fis_h2d));
+
+	cfis.fis_type = SATA_FIS_TYPE_REGISTER_H2D;
+	cfis.command = (is_write) ? ATA_CMD_WRITE : ATA_CMD_READ;
+	cfis.device = ATA_LBA;
+
+	cfis.device |= (block >> 24) & 0xf;
+	cfis.lba_high = (block >> 16) & 0xff;
+	cfis.lba_mid = (block >> 8) & 0xff;
+	cfis.lba_low = block & 0xff;
+	cfis.sector_count = (u8)(blkcnt & 0xff);
+
+	res = mv_ata_exec_ata_cmd(
+			port,&cfis,buffer,ATA_SECT_SIZE * blkcnt, is_write);
+
+	return res>=0?blkcnt:res;
+}
+
+u32 ata_low_level_rw(int dev, u32 blknr, u32 blkcnt, void *buffer,
+		int is_write){
+
+	struct mv_priv *priv =
+			(struct mv_priv *) sata_dev_desc[dev].priv;
+	u32 start, blks;
+	u8 *addr;
+	int max_blks;
+
+	debug("sata_low_level_rw: %d %d\n",blknr,blkcnt);
+
+	start = blknr;
+	blks = blkcnt;
+	addr = (u8 *)buffer;
+
+	max_blks = MV_ATA_MAX_SECTORS;
+	do {
+		if (blks > max_blks) {
+			if(sata_dev_desc[dev].lba48){
+				mv_sata_rw_cmd_ext(dev, start, max_blks, addr, is_write);
+			}else{
+				mv_sata_rw_cmd(dev, start, max_blks, addr, is_write);
+			}
+			start += max_blks;
+			blks -= max_blks;
+			addr += ATA_SECT_SIZE * max_blks;
+		} else {
+			if(sata_dev_desc[dev].lba48){
+				mv_sata_rw_cmd_ext(dev, start, blks, addr, is_write);
+			}else{
+				mv_sata_rw_cmd(dev, start, blks, addr, is_write);
+			}
+			start += blks;
+			blks = 0;
+			addr += ATA_SECT_SIZE * blks;
+		}
+	} while (blks != 0);
+
+	return blkcnt;
+}
+
+
+static int mv_ata_exec_ata_cmd_nondma(int port,
+		struct sata_fis_h2d *cfis, u8 *buffer, u32 len, u32 iswrite){
+	int i;
+	u16 *tp;
+	struct mv_priv *priv =
+			(struct mv_priv *) sata_dev_desc[port].priv;
+
+	debug("mv_ata_exec_ata_cmd_nondma\n");
+
+	out_le32(priv->regbase+PIO_SECTOR_COUNT,cfis->sector_count);
+	out_le32(priv->regbase+PIO_LBA_HI,cfis->lba_high);
+	out_le32(priv->regbase+PIO_LBA_MID,cfis->lba_mid);
+	out_le32(priv->regbase+PIO_LBA_LOW,cfis->lba_low);
+	out_le32(priv->regbase+PIO_ERR_FEATURES,cfis->features);
+	out_le32(priv->regbase+PIO_DEVICE,cfis->device);
+	out_le32(priv->regbase+PIO_CMD_STATUS,cfis->command);
+
+	mdelay(200);
+
+	if(ata_wait_register((u32 *)(priv->regbase+PIO_CMD_STATUS),ATA_BUSY,0x0,10000)){
+		debug("Failed to wait for completion\n");
+		return -1;
+	}
+
+	if(len>0){
+		tp=(u16*)buffer;
+		for ( i=0; i<len/2;i++){
+			if ( iswrite ){
+				out_le16(priv->regbase+PIO_DATA,*tp++);
+			}else{
+				*tp++ = in_le16(priv->regbase+PIO_DATA);
+			}
+		}
+	}
+	return len;
+}
+
+static int mv_sata_identify(int port, u16 *id){
+	struct sata_fis_h2d h2d;
+
+	memset(&h2d, 0, sizeof(struct sata_fis_h2d));
+
+	h2d.fis_type = SATA_FIS_TYPE_REGISTER_H2D;
+	h2d.command = ATA_CMD_ID_ATA;
+
+	return mv_ata_exec_ata_cmd_nondma(port,&h2d,(u8 *) id, ATA_ID_WORDS * 2, READ_CMD);
+}
+
+static void mv_sata_xfer_mode(int port, u16 *id){
+	struct mv_priv *priv = (struct mv_priv *) sata_dev_desc[port].priv;
+
+	priv->pio = id[ATA_ID_PIO_MODES];
+	priv->mwdma = id[ATA_ID_MWDMA_MODES];
+	priv->udma = id[ATA_ID_UDMA_MODES];
+	debug("pio %04x, mwdma %04x, udma %04x\n\r"
+			,priv->pio, priv->mwdma, priv->udma);
+}
+
+static void mv_sata_set_features(int port){
+	struct mv_priv *priv =
+			(struct mv_priv *) sata_dev_desc[port].priv;
+	struct sata_fis_h2d cfis;
+	u8 udma_cap;
+
+	memset(&cfis, 0, sizeof(struct sata_fis_h2d));
+
+	cfis.fis_type = SATA_FIS_TYPE_REGISTER_H2D;
+	cfis.command = ATA_CMD_SET_FEATURES;
+	cfis.features = SETFEATURES_XFER;
+
+	/* First check the device capablity */
+	udma_cap = (u8)(priv->udma & 0xff);
+
+	if (udma_cap == ATA_UDMA6)
+		cfis.sector_count = XFER_UDMA_6;
+	if (udma_cap == ATA_UDMA5)
+		cfis.sector_count = XFER_UDMA_5;
+	if (udma_cap == ATA_UDMA4)
+		cfis.sector_count = XFER_UDMA_4;
+	if (udma_cap == ATA_UDMA3)
+		cfis.sector_count = XFER_UDMA_3;
+
+	mv_ata_exec_ata_cmd_nondma(port, &cfis, NULL, 0, READ_CMD);
+}
+
+
+ulong sata_read(int dev, ulong blknr, ulong blkcnt, void *buffer){
+
+	return ata_low_level_rw(dev, blknr, blkcnt, buffer, READ_CMD);
+
+}
+
+ulong sata_write(int dev, ulong blknr, ulong blkcnt, const void *buffer){
+
+	return ata_low_level_rw(dev, blknr, blkcnt, buffer, WRITE_CMD);
+
+}
+
+int init_sata(int dev){
+	struct mv_priv *priv;
+
+	debug("Initialize sata dev: %d\n\r",dev);
+
+	if ( dev < 0 || dev >= CONFIG_SYS_SATA_MAX_DEVICE) {
+		printf("Invalid sata device %d\n\r",dev);
+		return -1;
+	}
+
+	priv = (struct mv_priv*)  malloc(sizeof(struct mv_priv));
+	if ( !priv ){
+		printf("Failed to allocate memory for private sata data\n\r");
+		return -1;
+	}
+
+	memset((void*) priv,0 , sizeof(struct mv_priv));
+
+#ifdef MALLOC_QUEUE
+	/* Allocate and align request buffer */
+	priv->crqb_alloc=malloc(sizeof(struct crqb)*REQUEST_QUEUE_SIZE
+						+CRQB_ALIGN);
+	if( !priv->crqb_alloc ){
+		printf("Unable to allocate memory for request queue\n\r");
+		return -1;
+	}
+	memset(priv->crqb_alloc ,0 ,sizeof(struct crqb)*REQUEST_QUEUE_SIZE+CRQB_ALIGN);
+	priv->request=(struct crqb *) (((u32)priv->crqb_alloc + CRQB_ALIGN)
+						& ~(CRQB_ALIGN-1));
+
+	/* Allocate and align response buffer */
+	priv->crpb_alloc=malloc(sizeof(struct crpb)*REQUEST_QUEUE_SIZE+CRPB_ALIGN);
+	if( !priv->crpb_alloc ){
+		printf("Unable to allocate memory for response queue\n\r");
+		return -1;
+	}
+	memset(priv->crpb_alloc ,0 ,sizeof(struct crpb)*REQUEST_QUEUE_SIZE+CRPB_ALIGN);
+	priv->response=(struct crpb *) (((u32)priv->crpb_alloc + CRPB_ALIGN)
+						& ~(CRPB_ALIGN-1));
+#else
+	memset(&priv->request,0,sizeof(struct crqb)*REQUEST_QUEUE_SIZE);
+	memset(&priv->response,0,sizeof(struct crpb)*REQUEST_QUEUE_SIZE);
+#endif
+	sata_dev_desc[dev].priv = (void*)priv;
+
+	sprintf(priv->name, "SATA%d", dev);
+
+	priv->regbase = dev==0?KW_SATA0_BASE:KW_SATA1_BASE;
+
+	if ( !hw_init ){
+		debug("Initialize sata hw\n\r");
+		hw_init=1;
+		mv_reset_one_hc();
+	}
+
+	mv_reset_port(dev);
+
+	if(probe_port(dev)){
+		priv->link=0;
+		return -1;
+	}
+	priv->link=1;
+
+	return 0;
+}
+
+int scan_sata(int port){
+	unsigned char serial[ATA_ID_SERNO_LEN + 1];
+	unsigned char firmware[ATA_ID_FW_REV_LEN + 1];
+	unsigned char product[ATA_ID_PROD_LEN + 1];
+	u64 n_sectors;
+	u16 *id;
+	struct mv_priv *priv =
+			(struct mv_priv *) sata_dev_desc[port].priv;
+
+	if ( !priv->link ){
+		return -1;
+	}
+	id = (u16 *) malloc(ATA_ID_WORDS*2);
+	if ( !id ){
+		printf("Failed to malloc id data\n");
+		return -1;
+	}
+
+	mv_sata_identify(port,id);
+
+	ata_swap_buf_le16(id, ATA_ID_WORDS);
+#ifdef DEBUG
+	ata_dump_id(id);
+#endif
+
+	/* Serial number */
+	ata_id_c_string(id, serial, ATA_ID_SERNO, sizeof(serial));
+	memcpy(sata_dev_desc[port].product, serial, sizeof(serial));
+
+	/* Firmware version */
+	ata_id_c_string(id, firmware, ATA_ID_FW_REV, sizeof(firmware));
+	memcpy(sata_dev_desc[port].revision, firmware, sizeof(firmware));
+
+	/* Product model */
+	ata_id_c_string(id, product, ATA_ID_PROD, sizeof(product));
+	memcpy(sata_dev_desc[port].vendor, product, sizeof(product));
+
+	/* Totoal sectors */
+	n_sectors = ata_id_n_sectors(id);
+	sata_dev_desc[port].lba = (u32)n_sectors;
+
+	/* Check if support LBA48 */
+	if (ata_id_has_lba48(id)) {
+		sata_dev_desc[port].lba48 = 1;
+		debug("Device support LBA48\n\r");
+	}
+
+	/* Get the NCQ queue depth from device */
+	priv->queue_depth = ata_id_queue_depth(id);
+
+	/* Get the xfer mode from device */
+	mv_sata_xfer_mode(port, id);
+
+	/* Set the xfer mode to highest speed */
+	mv_sata_set_features(port);
+
+	/* Start up */
+	mv_start_edma_engine(port);
+
+	return 0;
+}
diff --git a/drivers/block/sata_mv.h b/drivers/block/sata_mv.h
new file mode 100644
index 0000000..f6d95c3
--- /dev/null
+++ b/drivers/block/sata_mv.h
@@ -0,0 +1,229 @@
+/*
+ * Copyright (C) Excito Elektronik i Skåne AB, 2010.
+ * Author: Tor Krill <tor at excito.com>
+ *
+ * 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
+ *
+ */
+
+#ifndef MV_SATA_H
+#define MV_SATA_H
+#include <sata.h>
+#include <asm/arch/kirkwood.h>
+
+#define KW_SATA0_BASE	(KW_REGISTER(0x82000))
+#define KW_SATA1_BASE	(KW_REGISTER(0x84000))
+#define KW_SATAHC_BASE	(KW_REGISTER(0x80000))
+
+/* EDMA registers */
+#define EDMA_CFG			0x000
+#define EDMA_CFG_NCQ		(1<<5)
+#define EDMA_CFG_EQUE		(1<<9)
+#define EDMA_TIMER			0x004
+#define EDMA_IECR			0x008
+#define EDMA_IEMR			0x00c
+#define EDMA_RQBA_HI		0x010
+#define EDMA_RQIPR			0x014
+#define EDMA_RQIPR_IPMASK	(0x1f<<5)
+#define EDMA_RQIPR_IPSHIFT	5
+#define EDMA_RQOPR			0x018
+#define EDMA_RQOPR_OPMASK	(0x1f<<5)
+#define EDMA_RQOPR_OPSHIFT	5
+#define EDMA_RSBA_HI		0x01c
+#define EDMA_RSIPR			0x020
+#define EDMA_RSIPR_IPMASK	(0x1f<<3)
+#define EDMA_RSIPR_IPSHIFT	3
+#define	EDMA_RSOPR			0x024
+#define EDMA_RSOPR_OPMASK	(0x1f<<3)
+#define EDMA_RSOPR_OPSHIFT	3
+#define EDMA_CMD			0x028
+#define EDMA_CMD_ENEDMA		(0x01<<0)
+#define EDMA_CMD_DISEDMA	(0x01<<1)
+#define EDMA_CMD_ATARST		(0x01<<2)
+#define EDMA_CMD_FREEZE		(0x01<<4)
+#define EDMA_TEST_CTL		0x02c
+#define EDMA_STATUS			0x030
+#define EDMA_IORTO			0x034
+#define EDMA_CDTR			0x040
+#define EDMA_HLTCND			0x060
+#define EDMA_NTSR			0x094
+
+/* Basic DMA registers */
+#define BDMA_CMD		0x224
+#define BDMA_STATUS		0x228
+#define BDMA_DTLB		0x22c
+#define BDMA_DTHB		0x230
+#define BDMA_DRL		0x234
+#define BDMA_DRH		0x238
+
+/* SATA Interface registers */
+#define SIR_ICFG				0x050
+#define SIR_CFG_GEN2EN		(0x1<<7)
+#define SIR_PLL_CFG			0x054
+#define SIR_SSTATUS			0x300
+#define SSTATUS_DET_MASK	(0x0f<<0)
+#define SIR_SERROR			0x304
+#define SIR_SCONTROL		0x308
+#define SIR_SCONTROL_DETEN	(0x01<<0)
+#define SIR_LTMODE			0x30c
+#define SIR_LTMODE_NELBE	(0x01<<7)
+#define SIR_PHYMODE3		0x310
+#define SIR_PHYMODE4		0x314
+#define SIR_PHYMODE1		0x32c
+#define SIR_PHYMODE2		0x330
+#define SIR_BIST_CTRL		0x334
+#define SIR_BIST_DW1		0x338
+#define SIR_BIST_DW2		0x33c
+#define SIR_SERR_IRQ_MASK	0x340
+#define SIR_SATA_IFCTRL		0x344
+#define SIR_SATA_TESTCTRL	0x348
+#define SIR_SATA_IFSTATUS	0x34c
+#define SIR_VEND_UNIQ		0x35c
+#define SIR_FIS_CFG			0x360
+#define SIR_FIS_IRQ_CAUSE	0x364
+#define SIR_FIS_IRQ_MASK	0x368
+#define SIR_FIS_DWORD0		0x370
+#define SIR_FIS_DWORD1		0x374
+#define SIR_FIS_DWORD2		0x378
+#define SIR_FIS_DWORD3		0x37c
+#define SIR_FIS_DWORD4		0x380
+#define SIR_FIS_DWORD5		0x384
+#define SIR_FIS_DWORD6		0x388
+#define SIR_PHYM9_GEN2		0x398
+#define SIR_PHYM9_GEN1		0x39c
+#define SIR_PHY_CFG			0x3a0
+#define SIR_PHYCTL			0x3a4
+#define SIR_PHYM10			0x3a8
+#define SIR_PHYM12			0x3b0
+
+/* Shadow registers */
+#define	PIO_DATA			0x100
+#define PIO_ERR_FEATURES	0x104
+#define PIO_SECTOR_COUNT	0x108
+#define PIO_LBA_LOW			0x10c
+#define PIO_LBA_MID			0x110
+#define PIO_LBA_HI			0x114
+#define PIO_DEVICE			0x118
+#define PIO_CMD_STATUS		0x11c
+#define PIO_STATUS_ERR		(0x01<<0)
+#define PIO_STATUS_DRQ		(0x01<<3)
+#define PIO_STATUS_DF		(0x01<<5)
+#define PIO_STATUS_DRDY		(0x01<<6)
+#define PIO_STATUS_BSY		(0x01<<7)
+#define PIO_CTRL_ALTSTAT	0x120
+
+/* SATAHC arbiter registers */
+#define SATAHC_CFG			0x000
+#define SATAHC_RQOP			0x004
+#define SATAHC_RQIP			0x008
+#define SATAHC_ICT			0x00c
+#define SATAHC_ITT			0x010
+#define SATAHC_ICR			0x014
+#define SATAHC_ICR_PORT0	(0x01<<0)
+#define SATAHC_ICR_PORT1	(0x01<<1)
+#define SATAHC_MIC			0x020
+#define SATAHC_MIM			0x024
+#define SATAHC_LED_CFG		0x02c
+#define SATAHC_WIN0_CTRL	0x030
+#define SATAHC_WIN0_BASE	0x034
+#define SATAHC_WIN1_CTRL	0x040
+#define SATAHC_WIN1_BASE	0x044
+#define SATAHC_WIN2_CTRL	0x050
+#define SATAHC_WIN2_BASE	0x054
+#define SATAHC_WIN3_CTRL	0x060
+#define SATAHC_WIN3_BASE	0x064
+
+#define REQUEST_QUEUE_SIZE	32
+#define RESPONSE_QUEUE_SIZE REQUEST_QUEUE_SIZE
+
+struct crqb{
+	u32 dtb_low;		/* DW0 */
+	u32 dtb_high;		/* DW1 */
+	u32 control_flags;	/* DW2 */
+	u32 drb_count;		/* DW3 */
+	u32 ata_cmd_feat;	/* DW4 */
+	u32 ata_addr;		/* DW5 */
+	u32 ata_addr_exp;	/* DW6 */
+	u32 ata_sect_count;	/* DW7 */
+} __attribute__ ((packed));
+
+#define CRQB_ALIGN					0x400
+
+#define CRQB_CNTRLFLAGS_DIR			(0x01<<0)
+#define CRQB_CNTRLFLAGS_DQTAGMASK	(0x1f<<1)
+#define CRQB_CNTRLFLAGS_DQTAGSHIFT	1
+#define CRQB_CNTRLFLAGS_PMPORTMASK	(0x0f<<12)
+#define CRQB_CNTRLFLAGS_PMPORTSHIFT	12
+#define CRQB_CNTRLFLAGS_PRDMODE		(0x01<<16)
+#define CRQB_CNTRLFLAGS_HQTAGMASK	(0x1f<<17)
+#define CRQB_CNTRLFLAGS_HQTAGSHIFT	17
+
+#define CRQB_CMDFEAT_CMDMASK		(0xff<<16)
+#define CRQB_CMDFEAT_CMDSHIFT		16
+#define CRQB_CMDFEAT_FEATMASK		(0xff<<16)
+#define CRQB_CMDFEAT_FEATSHIFT		24
+
+#define CRQB_ADDR_LBA_LOWMASK		(0xff<<0)
+#define CRQB_ADDR_LBA_LOWSHIFT		0
+#define CRQB_ADDR_LBA_MIDMASK		(0xff<<8)
+#define CRQB_ADDR_LBA_MIDSHIFT		8
+#define CRQB_ADDR_LBA_HIGHMASK		(0xff<<16)
+#define CRQB_ADDR_LBA_HIGHSHIFT		16
+#define CRQB_ADDR_DEVICE_MASK		(0xff<<24)
+#define CRQB_ADDR_DEVICE_SHIFT		24
+
+#define CRQB_ADDR_LBA_LOW_EXP_MASK		(0xff<<0)
+#define CRQB_ADDR_LBA_LOW_EXP_SHIFT		0
+#define CRQB_ADDR_LBA_MID_EXP_MASK		(0xff<<8)
+#define CRQB_ADDR_LBA_MID_EXP_SHIFT		8
+#define CRQB_ADDR_LBA_HIGH_EXP_MASK		(0xff<<16)
+#define CRQB_ADDR_LBA_HIGH_EXP_SHIFT	16
+#define CRQB_ADDR_FEATURE_EXP__MASK		(0xff<<24)
+#define CRQB_ADDR_FEATURE_EXP__SHIFT	24
+
+#define CRQB_SECTCOUNT_COUNT_MASK		(0xff<<0)
+#define CRQB_SECTCOUNT_COUNT_SHIFT		0
+#define CRQB_SECTCOUNT_COUNT_EXP_MASK	(0xff<<8)
+#define CRQB_SECTCOUNT_COUNT_EXP_SHIFT	8
+
+struct eprd{
+	u32 phyaddr_low;
+	u32 bytecount_eot;
+	u32 phyaddr_hi;
+	u32 reserved;
+} __attribute__ ((packed));
+
+#define EPRD_PHYADDR_MASK	0xfffffffe
+#define EPRD_BYTECOUNT_MASK	0x0000ffff
+#define EPRD_EOT			(0x01<<31)
+
+struct crpb{
+	u32	id;
+	u32	flags;
+	u32 timestamp;
+} __attribute__ ((packed));
+#define CRPB_ALIGN		0x100
+
+#define READ_CMD	0
+#define WRITE_CMD	1
+
+/*
+ * Since we don't use PRDs yet max transfer size
+ * is 64KB
+ */
+#define MV_ATA_MAX_SECTORS	(65535/ATA_SECT_SIZE)
+
+#endif
-- 
1.7.0.4



More information about the U-Boot mailing list