summaryrefslogtreecommitdiffstats
path: root/arch/arm/boards/freescale-mx51-pdk/spi.c
diff options
context:
space:
mode:
Diffstat (limited to 'arch/arm/boards/freescale-mx51-pdk/spi.c')
-rw-r--r--arch/arm/boards/freescale-mx51-pdk/spi.c340
1 files changed, 340 insertions, 0 deletions
diff --git a/arch/arm/boards/freescale-mx51-pdk/spi.c b/arch/arm/boards/freescale-mx51-pdk/spi.c
new file mode 100644
index 0000000000..8eabe817d9
--- /dev/null
+++ b/arch/arm/boards/freescale-mx51-pdk/spi.c
@@ -0,0 +1,340 @@
+#include <common.h>
+#include <init.h>
+#include <asm/io.h>
+#include <mach/imx-regs.h>
+#include <gpio.h>
+
+#define IMX_SPI_ACTIVE_HIGH 1
+#define SPI_RETRY_TIMES 100
+#define CLKCTL_CACRR 0x10
+#define REV_ATLAS_LITE_2_0 0x10
+
+/* Only for SPI master support */
+struct imx_spi_dev {
+ unsigned int base; // base address of SPI module the device is connected to
+ unsigned int freq; // desired clock freq in Hz for this device
+ unsigned int ss_pol; // ss polarity: 1=active high; 0=active low
+ unsigned int ss; // slave select
+ unsigned int in_sctl; // inactive sclk ctl: 1=stay low; 0=stay high
+ unsigned int in_dctl; // inactive data ctl: 1=stay low; 0=stay high
+ unsigned int ssctl; // single burst mode vs multiple: 0=single; 1=multi
+ unsigned int sclkpol; // sclk polarity: active high=0; active low=1
+ unsigned int sclkpha; // sclk phase: 0=phase 0; 1=phase1
+ unsigned int fifo_sz; // fifo size in bytes for either tx or rx. Don't add them up!
+ unsigned int ctrl_reg;
+ unsigned int cfg_reg;
+};
+
+struct imx_spi_dev imx_spi_pmic = {
+ .base = MX51_CSPI1_BASE_ADDR,
+ .freq = 25000000,
+ .ss_pol = IMX_SPI_ACTIVE_HIGH,
+ .ss = 0, /* slave select 0 */
+ .fifo_sz = 32,
+};
+
+/*
+ * Initialization function for a spi slave device. It must be called BEFORE
+ * any spi operations. The SPI module will be -disabled- after this call.
+ */
+static int imx_spi_init(struct imx_spi_dev *dev)
+{
+ unsigned int clk_src = 66500000;
+ unsigned int pre_div = 0, post_div = 0, i, reg_ctrl = 0, reg_config = 0;
+
+ if (dev->freq == 0) {
+ printf("Error: desired clock is 0\n");
+ return -1;
+ }
+
+ /* control register setup */
+ if (clk_src > dev->freq) {
+ pre_div = clk_src / dev->freq;
+ if (pre_div > 16) {
+ post_div = pre_div / 16;
+ pre_div = 15;
+ }
+ if (post_div != 0) {
+ for (i = 0; i < 16; i++) {
+ if ((1 << i) >= post_div)
+ break;
+ }
+ if (i == 16) {
+ printf
+ ("Error: no divider can meet the freq: %d\n",
+ dev->freq);
+ return -1;
+ }
+ post_div = i;
+ }
+ }
+ debug("pre_div = %d, post_div=%d\n", pre_div, post_div);
+ reg_ctrl |= pre_div << 12;
+ reg_ctrl |= post_div << 8;
+ reg_ctrl |= 1 << (dev->ss + 4); /* always set to master mode */
+
+ /* configuration register setup */
+ reg_config |= dev->ss_pol << (dev->ss + 12);
+ reg_config |= dev->in_sctl << (dev->ss + 20);
+ reg_config |= dev->in_dctl << (dev->ss + 16);
+ reg_config |= dev->ssctl << (dev->ss + 8);
+ reg_config |= dev->sclkpol << (dev->ss + 4);
+ reg_config |= dev->sclkpha << (dev->ss + 0);
+
+ debug("reg_ctrl = 0x%x\n", reg_ctrl);
+ /* reset the spi */
+ writel(0, dev->base + 0x8);
+ writel(reg_ctrl, dev->base + 0x8);
+ debug("reg_config = 0x%x\n", reg_config);
+ writel(reg_config, dev->base + 0xC);
+ /* save control register */
+ dev->cfg_reg = reg_config;
+ dev->ctrl_reg = reg_ctrl;
+
+ /* clear interrupt reg */
+ writel(0, dev->base + 0x10);
+ writel(3 << 6, dev->base + 0x18);
+
+ return 0;
+}
+
+static int imx_spi_xfer(struct imx_spi_dev *dev, /* spi device pointer */
+ void *tx_buf, /* tx buffer (has to be 4-byte aligned) */
+ void *rx_buf, /* rx buffer (has to be 4-byte aligned) */
+ int burst_bits /* total number of bits in one burst (or xfer) */
+ )
+{
+ int val = SPI_RETRY_TIMES;
+ unsigned int *p_buf;
+ unsigned int reg;
+ int len, ret_val = 0;
+ int burst_bytes = burst_bits / 8;
+
+ /* Account for rounding of non-byte aligned burst sizes */
+ if ((burst_bits % 8) != 0)
+ burst_bytes++;
+
+ if (burst_bytes > dev->fifo_sz) {
+ printf("Error: maximum burst size is 0x%x bytes, asking 0x%x\n",
+ dev->fifo_sz, burst_bytes);
+ return -1;
+ }
+
+ dev->ctrl_reg = (dev->ctrl_reg & ~0xFFF00000) | ((burst_bits - 1) << 20);
+ writel(dev->ctrl_reg | 0x1, dev->base + 0x8);
+ writel(dev->cfg_reg, dev->base + 0xC);
+ debug("ctrl_reg=0x%x, cfg_reg=0x%x\n",
+ readl(dev->base + 0x8), readl(dev->base + 0xC));
+
+ /* move data to the tx fifo */
+ len = burst_bytes;
+ for (p_buf = tx_buf; len > 0; p_buf++, len -= 4)
+ writel(*p_buf, dev->base + 0x4);
+
+ reg = readl(dev->base + 0x8);
+ reg |= (1 << 2); /* set xch bit */
+ writel(reg, dev->base + 0x8);
+
+ /* poll on the TC bit (transfer complete) */
+ while ((val-- > 0) && (readl(dev->base + 0x18) & (1 << 7)) == 0);
+
+ /* clear the TC bit */
+ writel(3 << 6, dev->base + 0x18);
+
+ if (val == 0) {
+ printf("Error: re-tried %d times without response. Give up\n",
+ SPI_RETRY_TIMES);
+ ret_val = -1;
+ goto error;
+ }
+
+ /* move data in the rx buf */
+ len = burst_bytes;
+ for (p_buf = rx_buf; len > 0; p_buf++, len -= 4)
+ *p_buf = readl(dev->base + 0x0);
+
+error:
+ writel(0, dev->base + 0x8);
+ return ret_val;
+}
+
+/*
+ * To read/write to a PMIC register. For write, it does another read for the
+ * actual register value.
+ *
+ * @param reg register number inside the PMIC
+ * @param val data to be written to the register; don't care for read
+ * @param write 0 for read; 1 for write
+ *
+ * @return the actual data in the PMIC register
+ */
+static unsigned int
+pmic_reg(unsigned int reg, unsigned int val, unsigned int write)
+{
+ static unsigned int pmic_tx, pmic_rx;
+
+ if (reg > 63 || write > 1) {
+ printf("<reg num> = %d is invalid. Should be less then 63\n",
+ reg);
+ return 0;
+ }
+ pmic_tx = (write << 31) | (reg << 25) | (val & 0x00FFFFFF);
+ debug("reg=0x%x, val=0x%08x\n", reg, pmic_tx);
+
+ imx_spi_xfer(&imx_spi_pmic, (unsigned char *) &pmic_tx,
+ (unsigned char *) &pmic_rx, (4 * 8));
+
+ if (write) {
+ pmic_tx &= ~(1 << 31);
+ imx_spi_xfer(&imx_spi_pmic, (unsigned char *) &pmic_tx,
+ (unsigned char *) &pmic_rx, (4 * 8));
+ }
+
+ return pmic_rx;
+}
+
+static void show_pmic_info(void)
+{
+ unsigned int rev_id;
+ char *rev;
+
+ rev_id = pmic_reg(7, 0, 0);
+
+ switch (rev_id & 0x1F) {
+ case 0x1: rev = "1.0"; break;
+ case 0x9: rev = "1.1"; break;
+ case 0xa: rev = "1.2"; break;
+ case 0x10:
+ if (((rev_id >> 9) & 0x3) == 0)
+ rev = "2.0";
+ else
+ rev = "2.0a";
+ break;
+ case 0x11: rev = "2.1"; break;
+ case 0x18: rev = "3.0"; break;
+ case 0x19: rev = "3.1"; break;
+ case 0x1a: rev = "3.2"; break;
+ case 0x2: rev = "3.2a"; break;
+ case 0x1b: rev = "3.3"; break;
+ case 0x1d: rev = "3.5"; break;
+ default: rev = "unknown"; break;
+ }
+
+ printf("PMIC ID: 0x%08x [Rev: %s]\n", rev_id, rev);
+}
+
+int babbage_power_init(void)
+{
+ unsigned int val;
+ unsigned int reg;
+
+ imx_spi_init(&imx_spi_pmic);
+
+ show_pmic_info();
+
+ /* Write needed to Power Gate 2 register */
+ val = pmic_reg(34, 0, 0);
+ val &= ~0x10000;
+ pmic_reg(34, val, 1);
+
+ /* Write needed to update Charger 0 */
+ pmic_reg(48, 0x0023807F, 1);
+
+ /* power up the system first */
+ pmic_reg(34, 0x00200000, 1);
+
+ if (1) {
+ /* Set core voltage to 1.1V */
+ val = pmic_reg(24, 0, 0);
+ val &= ~0x1f;
+ val |= 0x14;
+ pmic_reg(24, val, 1);
+
+ /* Setup VCC (SW2) to 1.25 */
+ val = pmic_reg(25, 0, 0);
+ val &= ~0x1f;
+ val |= 0x1a;
+ pmic_reg(25, val, 1);
+
+ /* Setup 1V2_DIG1 (SW3) to 1.25 */
+ val = pmic_reg(26, 0, 0);
+ val &= ~0x1f;
+ val |= 0x1a;
+ pmic_reg(26, val, 1);
+ udelay(50);
+ /* Raise the core frequency to 800MHz */
+ writel(0x0, MX51_CCM_BASE_ADDR + CLKCTL_CACRR);
+ } else {
+ /* TO 3.0 */
+ /* Setup VCC (SW2) to 1.225 */
+ val = pmic_reg(25, 0, 0);
+ val &= ~0x1f;
+ val |= 0x19;
+ pmic_reg(25, val, 1);
+
+ /* Setup 1V2_DIG1 (SW3) to 1.2 */
+ val = pmic_reg(26, 0, 0);
+ val &= ~0x1f;
+ val |= 0x18;
+ pmic_reg(26, val, 1);
+ }
+
+ if (((pmic_reg(7, 0, 0) & 0x1F) < REV_ATLAS_LITE_2_0)
+ || (((pmic_reg(7, 0, 0) >> 9) & 0x3) == 0)) {
+ /* Set switchers in PWM mode for Atlas 2.0 and lower */
+ /* Setup the switcher mode for SW1 & SW2 */
+ val = pmic_reg(28, 0, 0);
+ val &= ~0x3c0f;
+ val |= 0x1405;
+ pmic_reg(28, val, 1);
+
+ /* Setup the switcher mode for SW3 & SW4 */
+ val = pmic_reg(29, 0, 0);
+ val &= ~0xf0f;
+ val |= 0x505;
+ pmic_reg(29, val, 1);
+ } else {
+ /* Set switchers in Auto in NORMAL mode & STANDBY mode for Atlas 2.0a */
+ /* Setup the switcher mode for SW1 & SW2 */
+ val = pmic_reg(28, 0, 0);
+ val &= ~0x3c0f;
+ val |= 0x2008;
+ pmic_reg(28, val, 1);
+
+ /* Setup the switcher mode for SW3 & SW4 */
+ val = pmic_reg(29, 0, 0);
+ val &= ~0xf0f;
+ val |= 0x808;
+ pmic_reg(29, val, 1);
+ }
+
+ /* Set VDIG to 1.65V, VGEN3 to 1.8V, VCAM to 2.5V */
+ val = pmic_reg(30, 0, 0);
+ val &= ~0x34030;
+ val |= 0x10020;
+ pmic_reg(30, val, 1);
+
+ /* Set VVIDEO to 2.775V, VAUDIO to 3V, VSD to 3.15V */
+ val = pmic_reg(31, 0, 0);
+ val &= ~0x1FC;
+ val |= 0x1F4;
+ pmic_reg(31, val, 1);
+
+ /* Configure VGEN3 and VCAM regulators to use external PNP */
+ val = 0x208;
+ pmic_reg(33, val, 1);
+ udelay(200);
+
+ gpio_direction_output(32 + 14, 0); /* Lower reset line */
+
+ /* Enable VGEN3, VCAM, VAUDIO, VVIDEO, VSD regulators */
+ val = 0x49249;
+ pmic_reg(33, val, 1);
+
+ udelay(500);
+
+ gpio_set_value(32 + 14, 1);
+
+ return 0;
+}
+