527 lines
17 KiB
C
527 lines
17 KiB
C
/* Copyright (c) 2010, Code Aurora Forum. All rights reserved.
|
|
|
|
* Redistribution and use in source and binary forms, with or without
|
|
* modification, are permitted provided that the following conditions are
|
|
* met:
|
|
* * Redistributions of source code must retain the above copyright
|
|
* notice, this list of conditions and the following disclaimer.
|
|
* * Redistributions in binary form must reproduce the above
|
|
* copyright notice, this list of conditions and the following
|
|
* disclaimer in the documentation and/or other materials provided
|
|
* with the distribution.
|
|
* * Neither the name of Code Aurora Forum, Inc. nor the names of its
|
|
* contributors may be used to endorse or promote products derived
|
|
* from this software without specific prior written permission.
|
|
*
|
|
* THIS SOFTWARE IS PROVIDED "AS IS" AND ANY EXPRESS OR IMPLIED
|
|
* WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
|
|
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT
|
|
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS
|
|
* BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
|
|
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
|
|
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
|
|
* BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
|
|
* WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
|
|
* OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN
|
|
* IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
*/
|
|
|
|
#include <string.h>
|
|
#include <stdlib.h>
|
|
#include <debug.h>
|
|
#include <reg.h>
|
|
#include <platform/iomap.h>
|
|
#include <platform/irqs.h>
|
|
#include <platform/interrupts.h>
|
|
#include <platform/gpio_hw.h>
|
|
#include <dev/uart.h>
|
|
#include "uart_dm.h"
|
|
|
|
|
|
#ifndef NULL
|
|
#define NULL 0
|
|
#endif
|
|
|
|
/* Note:
|
|
* This is a basic implementation of UART_DM protocol. More focus has been
|
|
* given on simplicity than efficiency. Few of the things to be noted are:
|
|
* - RX path may not be suitable for multi-threaded scenaraio because of the
|
|
* use of static variables. TX path shouldn't have any problem though. If
|
|
* multi-threaded support is required, a simple data-structure can
|
|
* be maintained for each thread.
|
|
* - Right now we are using polling method than interrupt based.
|
|
* - We are using legacy UART protocol without Data Mover.
|
|
* - Not all interrupts and error events are handled.
|
|
* - While waiting Watchdog hasn't been taken into consideration.
|
|
*/
|
|
|
|
|
|
#define PACK_CHARS_INTO_WORDS(a, cnt, word) { \
|
|
word = 0; \
|
|
for(int j=0; j < (int)cnt; j++) \
|
|
{ \
|
|
word |= (a[j] & 0xff) \
|
|
<< (j * 8); \
|
|
} \
|
|
}
|
|
|
|
|
|
/* Static Function Prototype Declarations */
|
|
static unsigned int msm_boot_uart_config_gpios(void);
|
|
static unsigned int msm_boot_uart_dm_config_clock(void);
|
|
static unsigned int msm_boot_uart_dm_gsbi_init(void);
|
|
static unsigned int msm_boot_uart_replace_lr_with_cr(char* data_in,
|
|
int num_of_chars,
|
|
char *data_out,
|
|
int *num_of_chars_out);
|
|
static unsigned int msm_boot_uart_dm_init(void);
|
|
static unsigned int msm_boot_uart_dm_read(unsigned int* data,
|
|
int wait);
|
|
static unsigned int msm_boot_uart_dm_write(char* data,
|
|
unsigned int num_of_chars);
|
|
static unsigned int msm_boot_uart_dm_init_rx_transfer(void);
|
|
static unsigned int msm_boot_uart_dm_reset(void);
|
|
|
|
|
|
/* Extern functions */
|
|
void clock_config(unsigned int ns, unsigned int md,
|
|
unsigned int ns_addr, unsigned int md_addr);
|
|
|
|
void gpio_tlmm_config(uint32_t gpio, uint8_t func,
|
|
uint8_t dir, uint8_t pull,
|
|
uint8_t drvstr, uint32_t enable );
|
|
|
|
void udelay(unsigned usecs);
|
|
|
|
|
|
/*
|
|
* Helper function to replace Line Feed char "\n" with
|
|
* Carriage Return "\r\n".
|
|
* Currently keeping it simple than efficient
|
|
*/
|
|
static unsigned int msm_boot_uart_replace_lr_with_cr(char* data_in,
|
|
int num_of_chars,
|
|
char *data_out,
|
|
int *num_of_chars_out )
|
|
{
|
|
int i = 0, j = 0;
|
|
|
|
if ((data_in == NULL) || (data_out == NULL) || (num_of_chars < 0))
|
|
{
|
|
return MSM_BOOT_UART_DM_E_INVAL;
|
|
}
|
|
|
|
for (i=0, j=0; i < num_of_chars; i++, j++)
|
|
{
|
|
if ( data_in[i] == '\n' )
|
|
{
|
|
data_out[j++] = '\r';
|
|
}
|
|
|
|
data_out[j] = data_in[i];
|
|
}
|
|
|
|
*num_of_chars_out = j;
|
|
|
|
return MSM_BOOT_UART_DM_E_SUCCESS;
|
|
}
|
|
|
|
|
|
static unsigned int msm_boot_uart_dm_config_gpios(void)
|
|
{
|
|
/* GPIO Pin: MSM_BOOT_UART_DM_RX_GPIO (117)
|
|
Function: 2
|
|
Direction: IN
|
|
Pull: No PULL
|
|
Drive Strength: 8 ma
|
|
Output Enable: Disable
|
|
*/
|
|
gpio_tlmm_config(MSM_BOOT_UART_DM_RX_GPIO, 2, GPIO_INPUT,
|
|
GPIO_NO_PULL, GPIO_8MA, GPIO_DISABLE);
|
|
|
|
/* GPIO Pin: MSM_BOOT_UART_DM_TX_GPIO (118)
|
|
Function: 2
|
|
Direction: OUT
|
|
Pull: No PULL
|
|
Drive Strength: 8 ma
|
|
Output Enable: Disable
|
|
*/
|
|
gpio_tlmm_config(MSM_BOOT_UART_DM_TX_GPIO, 2, GPIO_OUTPUT,
|
|
GPIO_NO_PULL, GPIO_8MA, GPIO_DISABLE);
|
|
|
|
return MSM_BOOT_UART_DM_E_SUCCESS;
|
|
}
|
|
|
|
|
|
|
|
static unsigned int msm_boot_uart_dm_config_clock(void)
|
|
{
|
|
unsigned int curr_value = 0;
|
|
|
|
/* Vote for PLL8 to be enabled */
|
|
curr_value = readl(MSM_BOOT_PLL_ENABLE_SC0);
|
|
curr_value |= (1 << 8);
|
|
writel(curr_value, MSM_BOOT_PLL_ENABLE_SC0);
|
|
|
|
/* Proceed only after PLL is enabled */
|
|
while (!(readl(MSM_BOOT_PLL8_STATUS) & (1<<16)));
|
|
|
|
/* PLL8 is enabled. Enable gsbi_uart_clk */
|
|
|
|
/* GSBI clock frequencies for UART protocol
|
|
* Operating mode gsbi_uart_clk
|
|
* UART up to 115.2 Kbps 1.8432 MHz
|
|
* UART up to 460.8 Kbps 7.3728 MHz
|
|
* UART up to 4 Mbit/s 64 MHz
|
|
*
|
|
|
|
* Choosing lowest supported value
|
|
* Rate (KHz) NS MD
|
|
* 3686400 0xFD940043 0x0006FD8E
|
|
*/
|
|
|
|
clock_config(0xFD940043, 0x0006FD8E,
|
|
MSM_BOOT_UART_DM_APPS_NS,
|
|
MSM_BOOT_UART_DM_APPS_MD);
|
|
|
|
/* Enable gsbi_pclk */
|
|
writel(0x10, MSM_BOOT_UART_DM_GSBI_HCLK_CTL);
|
|
|
|
return MSM_BOOT_UART_DM_E_SUCCESS;
|
|
}
|
|
|
|
|
|
/*
|
|
* Initialize and configure GSBI for operation
|
|
*/
|
|
static unsigned int msm_boot_uart_dm_gsbi_init(void)
|
|
{
|
|
/* Configure the clock block */
|
|
msm_boot_uart_dm_config_clock();
|
|
|
|
/* Configure TLMM/GPIO to provide connectivity between GSBI
|
|
product ports and chip pads */
|
|
msm_boot_uart_dm_config_gpios();
|
|
|
|
|
|
/* Configure Data Mover for GSBI operation.
|
|
* Currently not supported. */
|
|
|
|
/* Configure GSBI for UART_DM protocol.
|
|
* I2C on 2 ports, UART (without HS flow control) on the other 2. */
|
|
writel(0x60, MSM_BOOT_GSBI_CTRL_REG);
|
|
|
|
return MSM_BOOT_UART_DM_E_SUCCESS;
|
|
}
|
|
|
|
/*
|
|
* Reset the UART
|
|
*/
|
|
static unsigned int msm_boot_uart_dm_reset(void)
|
|
{
|
|
writel(MSM_BOOT_UART_DM_CMD_RESET_RX, MSM_BOOT_UART_DM_CR);
|
|
writel(MSM_BOOT_UART_DM_CMD_RESET_TX, MSM_BOOT_UART_DM_CR);
|
|
writel(MSM_BOOT_UART_DM_CMD_RESET_ERR_STAT, MSM_BOOT_UART_DM_CR);
|
|
writel(MSM_BOOT_UART_DM_CMD_RES_TX_ERR, MSM_BOOT_UART_DM_CR);
|
|
writel(MSM_BOOT_UART_DM_CMD_RES_STALE_INT, MSM_BOOT_UART_DM_CR);
|
|
|
|
return MSM_BOOT_UART_DM_E_SUCCESS;
|
|
}
|
|
|
|
|
|
/*
|
|
* Initialize UART_DM - configure clock and required registers.
|
|
*/
|
|
static unsigned int msm_boot_uart_dm_init(void)
|
|
{
|
|
/* Configure GSB12 for uart dm */
|
|
msm_boot_uart_dm_gsbi_init();
|
|
|
|
|
|
/* Configure clock selection register for tx and rx rates.
|
|
* Selecting 115.2k for both RX and TX */
|
|
writel(MSM_BOOT_UART_DM_RX_TX_BIT_RATE, MSM_BOOT_UART_DM_CSR);
|
|
|
|
/* Configure UART mode registers MR1 and MR2 */
|
|
/* Hardware flow control isn't supported */
|
|
writel(0x0, MSM_BOOT_UART_DM_MR1);
|
|
|
|
/* 8-N-1 configuration: 8 data bits - No parity - 1 stop bit */
|
|
writel(MSM_BOOT_UART_DM_8_N_1_MODE, MSM_BOOT_UART_DM_MR2);
|
|
|
|
/* Configure Interrupt Mask register IMR */
|
|
writel(MSM_BOOT_UART_DM_IMR_ENABLED, MSM_BOOT_UART_DM_IMR);
|
|
|
|
/* Configure Tx and Rx watermarks configuration registers */
|
|
/* TX watermark value is set to 0 - interrupt is generated when
|
|
* FIFO level is less than or equal to 0 */
|
|
writel(MSM_BOOT_UART_DM_TFW_VALUE, MSM_BOOT_UART_DM_TFWR);
|
|
|
|
/* RX watermark value*/
|
|
writel(MSM_BOOT_UART_DM_RFW_VALUE, MSM_BOOT_UART_DM_RFWR);
|
|
|
|
/* Configure Interrupt Programming Register*/
|
|
/* Set initial Stale timeout value*/
|
|
writel(MSM_BOOT_UART_DM_STALE_TIMEOUT_LSB, MSM_BOOT_UART_DM_IPR);
|
|
|
|
/* Configure IRDA if required */
|
|
/* Disabling IRDA mode */
|
|
writel(0x0, MSM_BOOT_UART_DM_IRDA);
|
|
|
|
/* Configure and enable sim interface if required */
|
|
|
|
/* Configure hunt character value in HCR register */
|
|
/* Keep it in reset state */
|
|
writel(0x0, MSM_BOOT_UART_DM_HCR);
|
|
|
|
/* Configure Rx FIFO base address */
|
|
/* Both TX/RX shares same SRAM and default is half-n-half.
|
|
* Sticking with default value now.
|
|
* As such RAM size is (2^RAM_ADDR_WIDTH, 32-bit entries).
|
|
* We have found RAM_ADDR_WIDTH = 0x7f */
|
|
|
|
/* Issue soft reset command */
|
|
msm_boot_uart_dm_reset();
|
|
|
|
/* Enable/Disable Rx/Tx DM interfaces */
|
|
/* Data Mover not currently utilized. */
|
|
writel(0x0, MSM_BOOT_UART_DM_DMEN);
|
|
|
|
|
|
/* Enable transmitter and receiver */
|
|
writel(MSM_BOOT_UART_DM_CR_RX_ENABLE, MSM_BOOT_UART_DM_CR);
|
|
writel(MSM_BOOT_UART_DM_CR_TX_ENABLE, MSM_BOOT_UART_DM_CR);
|
|
|
|
/* Initialize Receive Path */
|
|
msm_boot_uart_dm_init_rx_transfer();
|
|
|
|
return MSM_BOOT_UART_DM_E_SUCCESS;
|
|
}
|
|
|
|
|
|
/*
|
|
* Initialize Receive Path
|
|
*/
|
|
static unsigned int msm_boot_uart_dm_init_rx_transfer(void)
|
|
{
|
|
writel(MSM_BOOT_UART_DM_GCMD_DIS_STALE_EVT, MSM_BOOT_UART_DM_CR);
|
|
writel(MSM_BOOT_UART_DM_CMD_RES_STALE_INT, MSM_BOOT_UART_DM_CR);
|
|
writel(MSM_BOOT_UART_DM_DMRX_DEF_VALUE, MSM_BOOT_UART_DM_DMRX);
|
|
writel(MSM_BOOT_UART_DM_GCMD_ENA_STALE_EVT, MSM_BOOT_UART_DM_CR);
|
|
|
|
return MSM_BOOT_UART_DM_E_SUCCESS;
|
|
}
|
|
|
|
/*
|
|
* UART Receive operation
|
|
* Reads a word from the RX FIFO.
|
|
*/
|
|
static unsigned int msm_boot_uart_dm_read(unsigned int* data, int wait)
|
|
{
|
|
static int rx_last_snap_count = 0;
|
|
static int rx_chars_read_since_last_xfer = 0;
|
|
|
|
if (data == NULL)
|
|
{
|
|
return MSM_BOOT_UART_DM_E_INVAL;
|
|
}
|
|
|
|
|
|
|
|
/* We will be polling RXRDY status bit */
|
|
while (!(readl(MSM_BOOT_UART_DM_SR) & MSM_BOOT_UART_DM_SR_RXRDY))
|
|
{
|
|
/* if this is not a blocking call, we'll just return */
|
|
if (!wait)
|
|
{
|
|
return MSM_BOOT_UART_DM_E_RX_NOT_READY;
|
|
}
|
|
}
|
|
|
|
/* Check for Overrun error. We'll just reset Error Status */
|
|
if (readl(MSM_BOOT_UART_DM_SR) & MSM_BOOT_UART_DM_SR_UART_OVERRUN)
|
|
{
|
|
writel(MSM_BOOT_UART_DM_CMD_RESET_ERR_STAT, MSM_BOOT_UART_DM_CR);
|
|
}
|
|
|
|
/* RX FIFO is ready; read a word. */
|
|
*data = readl(MSM_BOOT_UART_DM_RF(0));
|
|
|
|
/* increment the total count of chars we've read so far */
|
|
rx_chars_read_since_last_xfer += 4;
|
|
|
|
/* Rx transfer ends when one of the conditions is met:
|
|
* - The number of characters received since the end of the previous xfer
|
|
* equals the value written to DMRX at Transfer Initialization
|
|
* - A stale event occurred
|
|
*/
|
|
|
|
/* If RX transfer has not ended yet */
|
|
if (rx_last_snap_count == 0)
|
|
{
|
|
/* Check if we've received stale event */
|
|
if (readl(MSM_BOOT_UART_DM_MISR) & MSM_BOOT_UART_DM_RXSTALE)
|
|
{
|
|
/* Send command to reset stale interrupt */
|
|
writel(MSM_BOOT_UART_DM_CMD_RES_STALE_INT, MSM_BOOT_UART_DM_CR);
|
|
}
|
|
|
|
/* Check if we haven't read more than DMRX value */
|
|
else if ((unsigned int)rx_chars_read_since_last_xfer <
|
|
readl(MSM_BOOT_UART_DM_DMRX))
|
|
{
|
|
/* We can still continue reading before initializing RX transfer */
|
|
return MSM_BOOT_UART_DM_E_SUCCESS;
|
|
}
|
|
|
|
/* If we've reached here it means RX xfer end conditions been met */
|
|
|
|
/* Read UART_DM_RX_TOTAL_SNAP register to know how many valid chars
|
|
* we've read so far since last transfer */
|
|
rx_last_snap_count = readl(MSM_BOOT_UART_DM_RX_TOTAL_SNAP);
|
|
|
|
}
|
|
|
|
/* If there are still data left in FIFO we'll read them before
|
|
* initializing RX Transfer again */
|
|
if ((rx_last_snap_count - rx_chars_read_since_last_xfer) >= 0 )
|
|
{
|
|
return MSM_BOOT_UART_DM_E_SUCCESS;
|
|
}
|
|
|
|
msm_boot_uart_dm_init_rx_transfer();
|
|
rx_last_snap_count = 0;
|
|
rx_chars_read_since_last_xfer = 0;
|
|
|
|
return MSM_BOOT_UART_DM_E_SUCCESS;
|
|
}
|
|
|
|
|
|
/*
|
|
* UART transmit operation
|
|
*/
|
|
static unsigned int msm_boot_uart_dm_write(char* data,
|
|
unsigned int num_of_chars)
|
|
{
|
|
unsigned int tx_word_count = 0;
|
|
unsigned int tx_char_left = 0, tx_char = 0;
|
|
unsigned int tx_word = 0;
|
|
int i = 0;
|
|
char* tx_data = NULL;
|
|
char new_data[1024];
|
|
|
|
if ((data == NULL) || (num_of_chars <= 0))
|
|
{
|
|
return MSM_BOOT_UART_DM_E_INVAL;
|
|
}
|
|
|
|
/* Replace line-feed (/n) with carriage-return + line-feed (/r/n) */
|
|
|
|
msm_boot_uart_replace_lr_with_cr(data, num_of_chars, new_data, &i);
|
|
|
|
tx_data = new_data;
|
|
num_of_chars = i;
|
|
|
|
/* Write to NO_CHARS_FOR_TX register number of characters
|
|
* to be transmitted. However, before writing TX_FIFO must
|
|
* be empty as indicated by TX_READY interrupt in IMR register
|
|
*/
|
|
|
|
/* Check if transmit FIFO is empty.
|
|
* If not we'll wait for TX_READY interrupt. */
|
|
if (!(readl(MSM_BOOT_UART_DM_SR) & MSM_BOOT_UART_DM_SR_TXEMT))
|
|
{
|
|
while (!(readl(MSM_BOOT_UART_DM_ISR) & MSM_BOOT_UART_DM_TX_READY))
|
|
{
|
|
udelay(1);
|
|
/* Kick watchdog? */
|
|
}
|
|
}
|
|
|
|
/* We are here. FIFO is ready to be written. */
|
|
/* Write number of characters to be written */
|
|
writel(num_of_chars, MSM_BOOT_UART_DM_NO_CHARS_FOR_TX);
|
|
|
|
/* Clear TX_READY interrupt */
|
|
writel(MSM_BOOT_UART_DM_GCMD_RES_TX_RDY_INT, MSM_BOOT_UART_DM_CR);
|
|
|
|
/* We use four-character word FIFO. So we need to divide data into
|
|
* four characters and write in UART_DM_TF register */
|
|
tx_word_count = (num_of_chars % 4)? ((num_of_chars / 4) + 1) :
|
|
(num_of_chars / 4);
|
|
tx_char_left = num_of_chars;
|
|
|
|
for (i = 0; i < (int)tx_word_count; i++)
|
|
{
|
|
tx_char = (tx_char_left < 4)? tx_char_left : 4;
|
|
PACK_CHARS_INTO_WORDS(tx_data, tx_char, tx_word);
|
|
|
|
/* Wait till TX FIFO has space */
|
|
while (!(readl(MSM_BOOT_UART_DM_SR) & MSM_BOOT_UART_DM_SR_TXRDY))
|
|
{
|
|
udelay(1);
|
|
}
|
|
|
|
/* TX FIFO has space. Write the chars */
|
|
writel(tx_word, MSM_BOOT_UART_DM_TF(0));
|
|
tx_char_left = num_of_chars - (i+1)*4;
|
|
tx_data = tx_data + 4;
|
|
}
|
|
|
|
return MSM_BOOT_UART_DM_E_SUCCESS;
|
|
}
|
|
|
|
|
|
/* Defining functions that's exposed to outside world and in coformance to
|
|
* existing uart implemention. These functions are being called to initialize
|
|
* UART and print debug messages in bootloader. */
|
|
|
|
void uart_init(void)
|
|
{
|
|
char *data = "Android Bootloader - UART_DM Initialized!!!\n";
|
|
|
|
msm_boot_uart_dm_init();
|
|
msm_boot_uart_dm_write(data, 44);
|
|
|
|
}
|
|
|
|
/* UART_DM uses four character word FIFO where as UART core
|
|
* uses a character FIFO. so it's really inefficient to try
|
|
* to write single character. But that's how dprintf has been
|
|
* implemented.
|
|
*/
|
|
int uart_putc(int port, char c)
|
|
{
|
|
|
|
msm_boot_uart_dm_write(&c, 1);
|
|
|
|
return 0;
|
|
}
|
|
|
|
/* UART_DM uses four character word FIFO whereas uart_getc
|
|
* is supposed to read only one character. So we need to
|
|
* read a word and keep track of each character in the word.
|
|
*/
|
|
int uart_getc(int port, bool wait)
|
|
{
|
|
int byte;
|
|
static unsigned int word = 0;
|
|
|
|
if (!word)
|
|
{
|
|
/* Read from FIFO only if it's a first read or all the four
|
|
* characters out of a word have been read */
|
|
if (msm_boot_uart_dm_read( &word, wait) != MSM_BOOT_UART_DM_E_SUCCESS)
|
|
{
|
|
return -1;
|
|
}
|
|
|
|
}
|
|
|
|
byte = (int) word & 0xff;
|
|
word = word >> 8;
|
|
|
|
return byte;
|
|
}
|
|
|