/* 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 <sys/types.h> | |
#include <platform/iomap.h> | |
#include <platform/irqs.h> | |
#include <platform/interrupts.h> | |
#include <platform/clock.h> | |
#include <platform/gpio.h> | |
#include <uart_dm.h> | |
#include <gsbi.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_dm_gsbi_init(uint8_t id); | |
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(uint8_t id); | |
static unsigned int msm_boot_uart_dm_read(uint8_t id, unsigned int* data, | |
int wait); | |
static unsigned int msm_boot_uart_dm_write(uint8_t id, char* data, | |
unsigned int num_of_chars); | |
static unsigned int msm_boot_uart_dm_init_rx_transfer(uint8_t id); | |
static unsigned int msm_boot_uart_dm_reset(uint8_t id); | |
/* Keep track of gsbi vs port mapping. | |
*/ | |
static uint8_t gsbi_lookup[4]; | |
/* Extern functions */ | |
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; | |
} | |
/* | |
* Initialize and configure GSBI for operation | |
*/ | |
static unsigned int msm_boot_uart_dm_gsbi_init(uint8_t id) | |
{ | |
/* Configure the uart clock */ | |
clock_config_uart_dm(id); | |
/* Configure GPIO to provide connectivity between GSBI | |
product ports and chip pads */ | |
gpio_config_uart_dm(id); | |
/* 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(GSBI_PROTOCOL_CODE_I2C_UART << GSBI_CTRL_REG_PROTOCOL_CODE_S, | |
GSBI_CTRL_REG(id)); | |
/* Configure clock selection register for tx and rx rates. | |
* Selecting 115.2k for both RX and TX. | |
*/ | |
writel(UART_DM_CLK_RX_TX_BIT_RATE, MSM_BOOT_UART_DM_CSR(id)); | |
return MSM_BOOT_UART_DM_E_SUCCESS; | |
} | |
/* | |
* Reset the UART | |
*/ | |
static unsigned int msm_boot_uart_dm_reset(uint8_t id) | |
{ | |
writel(MSM_BOOT_UART_DM_CMD_RESET_RX, MSM_BOOT_UART_DM_CR(id)); | |
writel(MSM_BOOT_UART_DM_CMD_RESET_TX, MSM_BOOT_UART_DM_CR(id)); | |
writel(MSM_BOOT_UART_DM_CMD_RESET_ERR_STAT, MSM_BOOT_UART_DM_CR(id)); | |
writel(MSM_BOOT_UART_DM_CMD_RES_TX_ERR, MSM_BOOT_UART_DM_CR(id)); | |
writel(MSM_BOOT_UART_DM_CMD_RES_STALE_INT, MSM_BOOT_UART_DM_CR(id)); | |
return MSM_BOOT_UART_DM_E_SUCCESS; | |
} | |
/* | |
* Initialize UART_DM - configure clock and required registers. | |
*/ | |
static unsigned int msm_boot_uart_dm_init(uint8_t id) | |
{ | |
/* Configure GSBI for uart dm */ | |
msm_boot_uart_dm_gsbi_init(id); | |
/* Configure UART mode registers MR1 and MR2 */ | |
/* Hardware flow control isn't supported */ | |
writel(0x0, MSM_BOOT_UART_DM_MR1(id)); | |
/* 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(id)); | |
/* Configure Interrupt Mask register IMR */ | |
writel(MSM_BOOT_UART_DM_IMR_ENABLED, MSM_BOOT_UART_DM_IMR(id)); | |
/* 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(id)); | |
/* RX watermark value*/ | |
writel(MSM_BOOT_UART_DM_RFW_VALUE, MSM_BOOT_UART_DM_RFWR(id)); | |
/* Configure Interrupt Programming Register*/ | |
/* Set initial Stale timeout value*/ | |
writel(MSM_BOOT_UART_DM_STALE_TIMEOUT_LSB, MSM_BOOT_UART_DM_IPR(id)); | |
/* Configure IRDA if required */ | |
/* Disabling IRDA mode */ | |
writel(0x0, MSM_BOOT_UART_DM_IRDA(id)); | |
/* 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(id)); | |
/* 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(id); | |
/* Enable/Disable Rx/Tx DM interfaces */ | |
/* Data Mover not currently utilized. */ | |
writel(0x0, MSM_BOOT_UART_DM_DMEN(id)); | |
/* Enable transmitter and receiver */ | |
writel(MSM_BOOT_UART_DM_CR_RX_ENABLE, MSM_BOOT_UART_DM_CR(id)); | |
writel(MSM_BOOT_UART_DM_CR_TX_ENABLE, MSM_BOOT_UART_DM_CR(id)); | |
/* Initialize Receive Path */ | |
msm_boot_uart_dm_init_rx_transfer(id); | |
return MSM_BOOT_UART_DM_E_SUCCESS; | |
} | |
/* | |
* Initialize Receive Path | |
*/ | |
static unsigned int msm_boot_uart_dm_init_rx_transfer(uint8_t id) | |
{ | |
writel(MSM_BOOT_UART_DM_GCMD_DIS_STALE_EVT, MSM_BOOT_UART_DM_CR(id)); | |
writel(MSM_BOOT_UART_DM_CMD_RES_STALE_INT, MSM_BOOT_UART_DM_CR(id)); | |
writel(MSM_BOOT_UART_DM_DMRX_DEF_VALUE, MSM_BOOT_UART_DM_DMRX(id)); | |
writel(MSM_BOOT_UART_DM_GCMD_ENA_STALE_EVT, MSM_BOOT_UART_DM_CR(id)); | |
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(uint8_t id, 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(id)) & 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(id)) & MSM_BOOT_UART_DM_SR_UART_OVERRUN) | |
{ | |
writel(MSM_BOOT_UART_DM_CMD_RESET_ERR_STAT, MSM_BOOT_UART_DM_CR(id)); | |
} | |
/* RX FIFO is ready; read a word. */ | |
*data = readl(MSM_BOOT_UART_DM_RF(id, 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(id)) & 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(id)); | |
} | |
/* 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(id))) | |
{ | |
/* 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(id)); | |
} | |
/* 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(id); | |
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(uint8_t id, 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(id)) & MSM_BOOT_UART_DM_SR_TXEMT)) | |
{ | |
while (!(readl(MSM_BOOT_UART_DM_ISR(id)) & 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(id)); | |
/* Clear TX_READY interrupt */ | |
writel(MSM_BOOT_UART_DM_GCMD_RES_TX_RDY_INT, MSM_BOOT_UART_DM_CR(id)); | |
/* 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(id)) & MSM_BOOT_UART_DM_SR_TXRDY)) | |
{ | |
udelay(1); | |
} | |
/* TX FIFO has space. Write the chars */ | |
writel(tx_word, MSM_BOOT_UART_DM_TF(id, 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(uint8_t gsbi_id) | |
{ | |
static uint8_t port = 0; | |
char *data = "Android Bootloader - UART_DM Initialized!!!\n"; | |
msm_boot_uart_dm_init(gsbi_id); | |
msm_boot_uart_dm_write(gsbi_id, data, 44); | |
ASSERT(port < ARRAY_SIZE(gsbi_lookup)); | |
gsbi_lookup[port++] = gsbi_id; | |
} | |
/* 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) | |
{ | |
uint8_t gsbi_id = gsbi_lookup[port]; | |
msm_boot_uart_dm_write(gsbi_id, &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; | |
uint8_t gsbi_id = gsbi_lookup[port]; | |
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(gsbi_id, &word, wait) != | |
MSM_BOOT_UART_DM_E_SUCCESS) | |
{ | |
return -1; | |
} | |
} | |
byte = (int) word & 0xff; | |
word = word >> 8; | |
return byte; | |
} | |