rockbox/firmware/target/arm/imx233/ssp-imx233.c
Amaury Pouly f1c6c71218 imx233: improve ssp
Under some circumstance (timeout), the dma interrupt is not fired
and only the error one is. This can happen with some picky SD
cards and with the current code it causes a 1 second timeout.
This code properly catches the error interrupt to stop as soon
as possible.

Change-Id: I9c53ea272d01793f0f229571502e99eb62f1b723
2013-06-17 00:29:24 +02:00

388 lines
13 KiB
C

/***************************************************************************
* __________ __ ___.
* Open \______ \ ____ ____ | | _\_ |__ _______ ___
* Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ /
* Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < <
* Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \
* \/ \/ \/ \/ \/
* $Id$
*
* Copyright (C) 2011 by Amaury Pouly
*
* 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 software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
* KIND, either express or implied.
*
****************************************************************************/
#include "system.h"
#include "kernel.h"
#include "ssp-imx233.h"
#include "clkctrl-imx233.h"
#include "pinctrl-imx233.h"
#include "dma-imx233.h"
/* for debug purpose */
#if 0
#define ASSERT_SSP(ssp) if(ssp < 1 || ssp > 2) panicf("ssp=%d in %s", ssp, __func__);
#else
#define ASSERT_SSP(ssp) (void) ssp;
#endif
/* Hack to handle both single and multi devices at once */
#define SSP_SETn(reg, n, field) BF_SETn(reg, n, field)
#define SSP_CLRn(reg, n, field) BF_CLRn(reg, n, field)
#define SSP_RDn(reg, n, field) BF_RDn(reg, n, field)
#define SSP_WRn(reg, n, field, val) BF_WRn(reg, n, field, val)
#define SSP_WRn_V(reg, n, field, val) BF_WRn_V(reg, n, field, val)
#define SSP_REGn(reg, n) HW_##reg(n)
/* Used for DMA */
struct ssp_dma_command_t
{
struct apb_dma_command_t dma;
/* PIO words */
uint32_t ctrl0;
uint32_t cmd0;
uint32_t cmd1;
/* padded to next multiple of cache line size (32 bytes) */
uint32_t pad[2];
} __attribute__((packed)) CACHEALIGN_ATTR;
__ENSURE_STRUCT_CACHE_FRIENDLY(struct ssp_dma_command_t)
static bool ssp_in_use[IMX233_NR_SSP];
static int ssp_nr_in_use = 0;
static struct mutex ssp_mutex[IMX233_NR_SSP];
static struct semaphore ssp_sema[IMX233_NR_SSP];
static struct ssp_dma_command_t ssp_dma_cmd[IMX233_NR_SSP];
static uint32_t ssp_bus_width[IMX233_NR_SSP];
static unsigned ssp_log_block_size[IMX233_NR_SSP];
static ssp_detect_cb_t ssp_detect_cb[IMX233_NR_SSP];
static bool ssp_detect_invert[IMX233_NR_SSP];
void INT_SSP(int ssp, bool err)
{
/* reset dma channel on error */
if(imx233_dma_is_channel_error_irq(APB_SSP(ssp)) || err)
imx233_dma_reset_channel(APB_SSP(ssp));
/* clear irq flags */
imx233_dma_clear_channel_interrupt(APB_SSP(ssp));
SSP_CLRn(SSP_CTRL1, ssp, ALL_IRQ_EN);
semaphore_release(&ssp_sema[ssp - 1]);
}
void INT_SSP1_DMA(void)
{
INT_SSP(1, false);
}
void INT_SSP2_DMA(void)
{
INT_SSP(2, false);
}
void INT_SSP1_ERROR(void)
{
INT_SSP(1, true);
}
void INT_SSP2_ERROR(void)
{
INT_SSP(2, true);
}
void imx233_ssp_init(void)
{
/* power down and init data structures */
ssp_nr_in_use = 0;
for(int i = 0; i < IMX233_NR_SSP; i++)
{
SSP_SETn(SSP_CTRL0, 1 + i, CLKGATE);
semaphore_init(&ssp_sema[i], 1, 0);
mutex_init(&ssp_mutex[i]);
ssp_bus_width[i] = BV_SSP_CTRL0_BUS_WIDTH__ONE_BIT;
}
}
void imx233_ssp_start(int ssp)
{
ASSERT_SSP(ssp)
if(ssp_in_use[ssp - 1])
return;
ssp_in_use[ssp - 1] = true;
/* Gate block */
imx233_ssp_softreset(ssp);
/* Gate dma channel */
imx233_dma_clkgate_channel(APB_SSP(ssp), true);
/* If first block to start, start SSP clock */
if(ssp_nr_in_use == 0)
{
/** 2.3.1: the clk_ssp maximum frequency is 102.858 MHz */
/* fracdiv = 18 => clk_io = pll = 480Mhz
* intdiv = 5 => clk_ssp = 96Mhz */
imx233_clkctrl_enable(CLK_SSP, false);
imx233_clkctrl_set_div(CLK_SSP, 5);
imx233_clkctrl_set_bypass(CLK_SSP, false); /* use IO */
imx233_clkctrl_enable(CLK_SSP, true);
}
ssp_nr_in_use++;
}
void imx233_ssp_stop(int ssp)
{
ASSERT_SSP(ssp)
if(!ssp_in_use[ssp - 1])
return;
ssp_in_use[ssp - 1] = false;
/* Gate off */
SSP_SETn(SSP_CTRL0, ssp, CLKGATE);
/* Gate off dma */
imx233_dma_clkgate_channel(APB_SSP(ssp), false);
/* If last block to stop, stop SSP clock */
ssp_nr_in_use--;
if(ssp_nr_in_use == 0)
imx233_clkctrl_enable(CLK_SSP, false);
}
void imx233_ssp_softreset(int ssp)
{
ASSERT_SSP(ssp)
imx233_reset_block(&SSP_REGn(SSP_CTRL0, ssp));
}
void imx233_ssp_set_timings(int ssp, int divide, int rate, int timeout)
{
ASSERT_SSP(ssp)
SSP_REGn(SSP_TIMING, ssp) = BF_OR3(SSP_TIMING, CLOCK_DIVIDE(divide),
CLOCK_RATE(rate), TIMEOUT(timeout));
}
void imx233_ssp_setup_ssp1_sd_mmc_pins(bool enable_pullups, unsigned bus_width,
unsigned drive_strength, bool use_alt)
{
(void) use_alt;
/* SSP_{CMD,SCK} */
imx233_pinctrl_setup_vpin(VPIN_SSP1_CMD, "ssp1_cmd", drive_strength, enable_pullups);
imx233_pinctrl_setup_vpin(VPIN_SSP1_SCK, "ssp1_sck", drive_strength, false);
/* SSP_DATA{0-3} */
imx233_pinctrl_setup_vpin(VPIN_SSP1_D0, "ssp1_d0", drive_strength, enable_pullups);
if(bus_width >= 4)
{
imx233_pinctrl_setup_vpin(VPIN_SSP1_D1, "ssp1_d1", drive_strength, enable_pullups);
imx233_pinctrl_setup_vpin(VPIN_SSP1_D2, "ssp1_d2", drive_strength, enable_pullups);
imx233_pinctrl_setup_vpin(VPIN_SSP1_D3, "ssp1_d3", drive_strength, enable_pullups);
}
if(bus_width >= 8)
{
if(use_alt)
{
imx233_pinctrl_setup_vpin(VPIN_SSP1_D4_ALT, "ssp1_d4", drive_strength, enable_pullups);
imx233_pinctrl_setup_vpin(VPIN_SSP1_D5_ALT, "ssp1_d5", drive_strength, enable_pullups);
imx233_pinctrl_setup_vpin(VPIN_SSP1_D6_ALT, "ssp1_d6", drive_strength, enable_pullups);
imx233_pinctrl_setup_vpin(VPIN_SSP1_D7_ALT, "ssp1_d7", drive_strength, enable_pullups);
}
else
{
imx233_pinctrl_setup_vpin(VPIN_SSP1_D4, "ssp1_d4", drive_strength, enable_pullups);
imx233_pinctrl_setup_vpin(VPIN_SSP1_D5, "ssp1_d5", drive_strength, enable_pullups);
imx233_pinctrl_setup_vpin(VPIN_SSP1_D6, "ssp1_d6", drive_strength, enable_pullups);
imx233_pinctrl_setup_vpin(VPIN_SSP1_D7, "ssp1_d7", drive_strength, enable_pullups);
}
}
}
void imx233_ssp_setup_ssp2_sd_mmc_pins(bool enable_pullups, unsigned bus_width,
unsigned drive_strength)
{
(void) enable_pullups;
(void) bus_width;
(void) drive_strength;
/* SSP_{CMD,SCK} */
imx233_pinctrl_setup_vpin(VPIN_SSP2_CMD, "ssp2_cmd", drive_strength, enable_pullups);
imx233_pinctrl_setup_vpin(VPIN_SSP2_SCK, "ssp2_sck", drive_strength, false);
/* SSP_DATA{0-3} */
imx233_pinctrl_setup_vpin(VPIN_SSP2_D0, "ssp2_d0", drive_strength, enable_pullups);
if(bus_width >= 4)
{
imx233_pinctrl_setup_vpin(VPIN_SSP2_D1, "ssp2_d1", drive_strength, enable_pullups);
imx233_pinctrl_setup_vpin(VPIN_SSP2_D2, "ssp2_d2", drive_strength, enable_pullups);
imx233_pinctrl_setup_vpin(VPIN_SSP2_D3, "ssp2_d3", drive_strength, enable_pullups);
}
if(bus_width >= 8)
{
imx233_pinctrl_setup_vpin(VPIN_SSP2_D4, "ssp2_d4", drive_strength, enable_pullups);
imx233_pinctrl_setup_vpin(VPIN_SSP2_D5, "ssp2_d5", drive_strength, enable_pullups);
imx233_pinctrl_setup_vpin(VPIN_SSP2_D6, "ssp2_d6", drive_strength, enable_pullups);
imx233_pinctrl_setup_vpin(VPIN_SSP2_D7, "ssp2_d7", drive_strength, enable_pullups);
}
}
void imx233_ssp_set_mode(int ssp, unsigned mode)
{
ASSERT_SSP(ssp)
/* set mode */
SSP_WRn(SSP_CTRL1, ssp, SSP_MODE, mode);
/* set mode specific settings */
switch(mode)
{
case BV_SSP_CTRL1_SSP_MODE__SD_MMC:
SSP_WRn_V(SSP_CTRL1, ssp, WORD_LENGTH, EIGHT_BITS);
SSP_SETn(SSP_CTRL1, ssp, POLARITY);
SSP_SETn(SSP_CTRL1, ssp, DMA_ENABLE);
break;
default: return;
}
}
void imx233_ssp_set_bus_width(int ssp, unsigned width)
{
ASSERT_SSP(ssp)
switch(width)
{
case 1: ssp_bus_width[ssp - 1] = BV_SSP_CTRL0_BUS_WIDTH__ONE_BIT; break;
case 4: ssp_bus_width[ssp - 1] = BV_SSP_CTRL0_BUS_WIDTH__FOUR_BIT; break;
case 8: ssp_bus_width[ssp - 1] = BV_SSP_CTRL0_BUS_WIDTH__EIGHT_BIT; break;
}
}
void imx233_ssp_set_block_size(int ssp, unsigned log_block_size)
{
ASSERT_SSP(ssp)
ssp_log_block_size[ssp - 1] = log_block_size;
}
enum imx233_ssp_error_t imx233_ssp_sd_mmc_transfer(int ssp, uint8_t cmd,
uint32_t cmd_arg, enum imx233_ssp_resp_t resp, void *buffer, unsigned block_count,
bool wait4irq, bool read, uint32_t *resp_ptr)
{
ASSERT_SSP(ssp)
mutex_lock(&ssp_mutex[ssp - 1]);
/* Enable all interrupts */
imx233_dma_reset_channel(APB_SSP(ssp));
imx233_icoll_enable_interrupt(INT_SRC_SSP_DMA(ssp), true);
imx233_icoll_enable_interrupt(INT_SRC_SSP_ERROR(ssp), true);
imx233_dma_enable_channel_interrupt(APB_SSP(ssp), true);
unsigned xfer_size = block_count * (1 << ssp_log_block_size[ssp - 1]);
ssp_dma_cmd[ssp - 1].cmd0 = BF_OR4(SSP_CMD0, CMD(cmd), APPEND_8CYC(1),
BLOCK_SIZE(ssp_log_block_size[ssp - 1]), BLOCK_COUNT(block_count - 1));
ssp_dma_cmd[ssp - 1].cmd1 = cmd_arg;
/* setup all flags and run */
ssp_dma_cmd[ssp - 1].ctrl0 = BF_OR9(SSP_CTRL0, XFER_COUNT(xfer_size),
ENABLE(1), IGNORE_CRC(buffer == NULL), WAIT_FOR_IRQ(wait4irq),
GET_RESP(resp != SSP_NO_RESP), LONG_RESP(resp == SSP_LONG_RESP),
BUS_WIDTH(ssp_bus_width[ssp - 1]), DATA_XFER(buffer != NULL),
READ(read));
/* setup the dma parameters */
ssp_dma_cmd[ssp - 1].dma.buffer = buffer;
ssp_dma_cmd[ssp - 1].dma.next = NULL;
ssp_dma_cmd[ssp - 1].dma.cmd = BF_OR6(APB_CHx_CMD,
COMMAND(buffer == NULL ? BV_APB_CHx_CMD_COMMAND__NO_XFER :
read ? BV_APB_CHx_CMD_COMMAND__WRITE : BV_APB_CHx_CMD_COMMAND__READ),
IRQONCMPLT(1), SEMAPHORE(1), WAIT4ENDCMD(1), CMDWORDS(3), XFER_COUNT(xfer_size));
SSP_CLRn(SSP_CTRL1, ssp, ALL_IRQ);
SSP_SETn(SSP_CTRL1, ssp, ALL_IRQ_EN);
imx233_dma_start_command(APB_SSP(ssp), &ssp_dma_cmd[ssp - 1].dma);
/* the SSP hardware already has a timeout but we never know; 1 sec is a maximum
* for all operations */
enum imx233_ssp_error_t ret;
if(semaphore_wait(&ssp_sema[ssp - 1], HZ) == OBJ_WAIT_TIMEDOUT)
{
imx233_dma_reset_channel(APB_SSP(ssp));
ret = SSP_TIMEOUT;
}
else if((SSP_REGn(SSP_CTRL1, ssp) & BM_SSP_CTRL1_ALL_IRQ) == 0)
ret = SSP_SUCCESS;
else if((SSP_REGn(SSP_CTRL1, ssp) & BM_SSP_CTRL1_TIMEOUT_IRQ))
ret = SSP_TIMEOUT;
else
ret = SSP_ERROR;
if(resp_ptr != NULL)
{
if(resp != SSP_NO_RESP)
*resp_ptr++ = SSP_REGn(SSP_SDRESP0, ssp);
if(resp == SSP_LONG_RESP)
{
*resp_ptr++ = SSP_REGn(SSP_SDRESP1, ssp);
*resp_ptr++ = SSP_REGn(SSP_SDRESP2, ssp);
*resp_ptr++ = SSP_REGn(SSP_SDRESP3, ssp);
}
}
mutex_unlock(&ssp_mutex[ssp - 1]);
return ret;
}
void imx233_ssp_sd_mmc_power_up_sequence(int ssp)
{
ASSERT_SSP(ssp)
SSP_CLRn(SSP_CMD0, ssp, SLOW_CLKING_EN);
SSP_SETn(SSP_CMD0, ssp, CONT_CLKING_EN);
mdelay(1);
SSP_CLRn(SSP_CMD0, ssp, CONT_CLKING_EN);
}
static int ssp_detect_oneshot_callback(struct timeout *tmo)
{
int ssp = tmo->data;
ASSERT_SSP(ssp)
if(ssp_detect_cb[ssp - 1])
ssp_detect_cb[ssp - 1](ssp);
return 0;
}
static struct timeout ssp_detect_oneshot[2];
static void detect_irq(int bank, int pin, intptr_t ssp)
{
(void) bank;
(void) pin;
timeout_register(&ssp_detect_oneshot[ssp - 1], ssp_detect_oneshot_callback, (3*HZ/10), ssp);
}
void imx233_ssp_sdmmc_setup_detect(int ssp, bool enable, ssp_detect_cb_t fn,
bool first_time, bool invert)
{
ASSERT_SSP(ssp)
vpin_t vpin = VPIN_SSP1_DET;
if(ssp == 2)
vpin = VPIN_SSP2_DET;
unsigned bank = VPIN_UNPACK_BANK(vpin), pin = VPIN_UNPACK_PIN(vpin);
ssp_detect_cb[ssp - 1] = fn;
ssp_detect_invert[ssp - 1] = invert;
if(enable)
{
imx233_pinctrl_acquire(bank, pin, ssp == 1 ? "ssp1_det" : "ssp2_det");
imx233_pinctrl_set_function(bank, pin, PINCTRL_FUNCTION_GPIO);
imx233_pinctrl_enable_gpio(bank, pin, false);
}
if(first_time && imx233_ssp_sdmmc_detect(ssp))
detect_irq(bank, pin, ssp);
imx233_pinctrl_setup_irq(bank, pin, enable,
true, !imx233_ssp_sdmmc_detect_raw(ssp), detect_irq, ssp);
}
bool imx233_ssp_sdmmc_is_detect_inverted(int ssp)
{
ASSERT_SSP(ssp)
return ssp_detect_invert[ssp - 1];
}
bool imx233_ssp_sdmmc_detect_raw(int ssp)
{
ASSERT_SSP(ssp)
return SSP_RDn(SSP_STATUS, ssp, CARD_DETECT);
}
bool imx233_ssp_sdmmc_detect(int ssp)
{
ASSERT_SSP(ssp)
return imx233_ssp_sdmmc_detect_raw(ssp) != ssp_detect_invert[ssp - 1];
}