From 27fac88548366f57c32931ed4d7c7dfc5b4f1627 Mon Sep 17 00:00:00 2001 From: Michael Sevakis Date: Sun, 27 Apr 2008 10:30:54 +0000 Subject: Gigabeat S: Implement i2c driver - transmit works but no testing of receiving which will get a chance later. Add some seeds for codec driver. Correct a few #defines. git-svn-id: svn://svn.rockbox.org/rockbox/trunk@17254 a1c6a512-1295-4272-9138-f99709370657 --- firmware/target/arm/imx31/gigabeat-s/i2c-imx31.c | 313 ++++++++++++++++++++- firmware/target/arm/imx31/gigabeat-s/i2c-imx31.h | 56 +++- firmware/target/arm/imx31/gigabeat-s/pcm-imx31.c | 2 + .../target/arm/imx31/gigabeat-s/wmcodec-imx31.c | 25 +- 4 files changed, 377 insertions(+), 19 deletions(-) (limited to 'firmware/target/arm') diff --git a/firmware/target/arm/imx31/gigabeat-s/i2c-imx31.c b/firmware/target/arm/imx31/gigabeat-s/i2c-imx31.c index 54ddaa7a46..ccd9efb321 100644 --- a/firmware/target/arm/imx31/gigabeat-s/i2c-imx31.c +++ b/firmware/target/arm/imx31/gigabeat-s/i2c-imx31.c @@ -7,7 +7,7 @@ * \/ \/ \/ \/ \/ * $Id$ * - * Copyright (C) 2007 by Michael Sevakis + * Copyright (C) 2008 by Michael Sevakis * * All files in this archive are subject to the GNU General Public License. * See the file COPYING in the source tree root for full license agreement. @@ -16,38 +16,321 @@ * KIND, either express or implied. * ****************************************************************************/ +#include +#include "config.h" #include "system.h" +#include "kernel.h" +#include "avic-imx31.h" +#include "clkctl-imx31.h" #include "i2c-imx31.h" -#if 0 -static int i2c_getack(void) +/* Forward interrupt handler declarations */ +#if (I2C_MODULE_MASK & USE_I2C1_MODULE) +static __attribute__((interrupt("IRQ"))) void I2C1_HANDLER(void); +#endif +#if (I2C_MODULE_MASK & USE_I2C2_MODULE) +static __attribute__((interrupt("IRQ"))) void I2C2_HANDLER(void); +#endif +#if (I2C_MODULE_MASK & USE_I2C3_MODULE) +static __attribute__((interrupt("IRQ"))) void I2C3_HANDLER(void); +#endif + +static struct i2c_module_descriptor { - return 0; + struct i2c_map *base; /* Module base address */ + enum IMX31_CG_LIST cg; /* Clock gating index */ + enum IMX31_INT_LIST ints; /* Module interrupt number */ + int enable; /* Enable count */ + void (*handler)(void); /* Module interrupt handler */ + struct mutex m; /* Node mutual-exclusion */ + struct wakeup w; /* I2C done signal */ + unsigned char *addr_data; /* Additional addressing data */ + int addr_count; /* Addressing byte count */ + unsigned char *data; /* TX/RX buffer (actual data) */ + int data_count; /* TX/RX byte count */ + unsigned char addr; /* Address + r/w bit */ +} i2c_descs[I2C_NUM_I2C] = +{ +#if (I2C_MODULE_MASK & USE_I2C1_MODULE) + { + .base = (struct i2c_map *)I2C1_BASE_ADDR, + .cg = CG_I2C1, + .ints = I2C1, + .handler = I2C1_HANDLER, + }, +#endif +#if (I2C_MODULE_MASK & USE_I2C2_MODULE) + { + .base = (struct i2c_map *)I2C2_BASE_ADDR, + .cg = CG_I2C2, + .ints = I2C2, + .handler = I2C2_HANDLER, + }, +#endif +#if (I2C_MODULE_MASK & USE_I2C3_MODULE) + { + .base = (struct i2c_map *)I2C3_BASE_ADDR, + .cg = CG_I2C3, + .ints = I2C3, + .handler = I2C3_HANDLER, + }, +#endif +}; + +static void i2c_interrupt(enum i2c_module_number i2c) +{ + struct i2c_module_descriptor *const desc = &i2c_descs[i2c]; + struct i2c_map * const base = desc->base; + uint16_t i2sr = base->i2sr; + + base->i2sr = i2sr & ~I2C_I2SR_IIF; /* Clear IIF */ + + if (desc->addr_count >= 0) + { + /* ADDR cycle - either done or more to send */ + if ((i2sr & I2C_I2SR_RXAK) != 0) + { + goto i2c_stop; /* problem */ + } + + if (--desc->addr_count < 0) + { + /* Switching to data cycle */ + if (desc->addr & 0x1) + { + base->i2cr &= ~I2C_I2CR_MTX; /* Switch to RX mode */ + base->i2dr; /* Dummy read */ + return; + } + /* else remaining data is TX - handle below */ + goto i2c_transmit; + } + else + { + base->i2dr = *desc->addr_data++; /* Send next addressing byte */ + return; + } + } + + if (base->i2cr & I2C_I2CR_MTX) + { +i2c_transmit: + /* Transmitting data */ + if ((i2sr & I2C_I2SR_RXAK) == 0 && desc->data_count > 0) + { + /* More bytes to send, got ACK from previous byte */ + base->i2dr = *desc->data++; + desc->data_count--; + return; + } + /* else done or no ACK received */ + } + else + { + /* Receiving data */ + if (--desc->data_count > 0) + { + if (desc->data_count == 1) + { + /* 2nd to Last byte - NACK */ + base->i2cr |= I2C_I2CR_TXAK; + } + + *desc->data++ = base->i2dr; /* Read data from I2DR and store */ + return; + } + else + { + /* Generate STOP signal before reading data */ + base->i2cr &= ~(I2C_I2CR_MSTA | I2C_I2CR_IIEN); + *desc->data++ = base->i2dr; /* Read data from I2DR and store */ + goto i2c_done; + } + } + +i2c_stop: + /* Generate STOP signal */ + base->i2cr &= ~(I2C_I2CR_MSTA | I2C_I2CR_IIEN); +i2c_done: + /* Signal thread we're done */ + wakeup_signal(&desc->w); } -static int i2c_start(void) +#if (I2C_MODULE_MASK & USE_I2C1_MODULE) +static __attribute__((interrupt("IRQ"))) void I2C1_HANDLER(void) { - return 0; + i2c_interrupt(I2C1_NUM); } +#endif +#if (I2C_MODULE_MASK & USE_I2C2_MODULE) +static __attribute__((interrupt("IRQ"))) void I2C2_HANDLER(void) +{ + i2c_interrupt(I2C2_NUM); +} +#endif +#if (I2C_MODULE_MASK & USE_I2C3_MODULE) +static __attribute__((interrupt("IRQ"))) void I2C3_HANDLER(void) +{ + i2c_interrupt(I2C3_NUM); +} +#endif -static void i2c_stop(void) +static int i2c_transfer(struct i2c_node * const node, + struct i2c_module_descriptor *const desc) { + struct i2c_map * const base = desc->base; + int count = desc->data_count; + uint16_t i2cr; + + /* Set speed */ + base->ifdr = node->ifdr; + + /* Enable module */ + base->i2cr = I2C_I2CR_IEN; + + /* Enable Interrupt, Master */ + i2cr = I2C_I2CR_IEN | I2C_I2CR_IIEN | I2C_I2CR_MTX; + + if ((desc->addr & 0x1) && desc->data_count < 2) + { + /* Receiving less than two bytes - disable ACK generation */ + i2cr |= I2C_I2CR_TXAK; + } + + /* Set config */ + base->i2cr = i2cr; + + /* Generate START */ + base->i2cr |= I2C_I2CR_MSTA; + + /* Address slave (first byte sent) and begin session. */ + base->i2dr = desc->addr; + + /* Wait for transfer to complete */ + count = (wakeup_wait(&desc->w, HZ) != OBJ_WAIT_SUCCEEDED) ? + -1 : (count - desc->data_count); + + /* Disable module - generate STOP if timeout */ + base->i2cr = 0; + + return count; } -static int i2c_outb(unsigned char byte) +int i2c_read(struct i2c_node *node, int reg, + unsigned char *data, int data_count) { - (void)byte; - return 0; + struct i2c_module_descriptor *const desc = &i2c_descs[node->num]; + unsigned char ad[1]; + + mutex_lock(&desc->m); + + desc->addr = (node->addr & 0xfe) | 0x1; /* Slave address/rd */ + + if (reg >= 0) + { + /* Sub-address */ + desc->addr_count = 1; + desc->addr_data = ad; + ad[0] = reg; + } + /* else raw read from slave */ + + desc->data = data; + desc->data_count = data_count; + + data_count = i2c_transfer(node, desc); + + desc->addr_count = 0; /* To eliminate zeroing elsewhere */ + + mutex_unlock(&desc->m); + + return data_count; } -#endif -void i2c_write(int addr, const unsigned char *buf, int count) +int i2c_write(struct i2c_node *node, const unsigned char *data, int data_count) { - (void)addr; - (void)buf; - (void)count; + struct i2c_module_descriptor *const desc = &i2c_descs[node->num]; + + mutex_lock(&desc->m); + + desc->addr = node->addr & 0xfe; /* Slave address/wr */ + desc->data = (unsigned char *)data; + desc->data_count = data_count; + + data_count = i2c_transfer(node, desc); + + mutex_unlock(&desc->m); + + return data_count; } void i2c_init(void) { + int i; + + /* Do one-time inits for each module that will be used - leave + * module disabled and unclocked until something wants it */ + for (i = 0; i < I2C_NUM_I2C; i++) + { + struct i2c_module_descriptor *const desc = &i2c_descs[i]; + imx31_clkctl_module_clock_gating(desc->cg, CGM_ON_ALL); + mutex_init(&desc->m); + wakeup_init(&desc->w); + desc->base->i2cr = 0; + imx31_clkctl_module_clock_gating(desc->cg, CGM_OFF); + } + +#if 0 + /* Pad config set up by OF bootloader doesn't agree with manual but + * TX works at the moment - probably would't do this here either */ + uint32_t reg = SW_PAD_CTL_CSI_PIXCLK_I2C_CLK_I2C_DAT; + reg &= ~0xfffff; + reg |= (1 << 19) | (3 << 17) | (1 << 15) | (1 << 14) | + (1 << 13) | (0 << 11); + + reg |= (1 << 9) | (3 << 7) | (1 << 5) | (1 << 4) | + (1 << 3) | (0 << 1); + + SW_PAD_CTL_CSI_PIXCLK_I2C_CLK_I2C_DAT = reg; +#endif +} + +void i2c_enable_node(struct i2c_node *node, bool enable) +{ + struct i2c_module_descriptor *const desc = &i2c_descs[node->num]; + + mutex_lock(&desc->m); + + if (enable) + { + if (++desc->enable == 1) + { + /* First enable */ + imx31_clkctl_module_clock_gating(desc->cg, CGM_ON_ALL); + avic_enable_int(desc->ints, IRQ, 7, desc->handler); + } + } + else + { + if (desc->enable > 0 && --desc->enable == 0) + { + /* Last enable */ + avic_disable_int(desc->ints); + imx31_clkctl_module_clock_gating(desc->cg, CGM_OFF); + } + } + + mutex_unlock(&desc->m); +} + +void i2c_lock_node(struct i2c_node *node) +{ + struct i2c_module_descriptor *const desc = &i2c_descs[node->num]; + mutex_lock(&desc->m); +} + +void i2c_unlock_node(struct i2c_node *node) +{ + struct i2c_module_descriptor *const desc = &i2c_descs[node->num]; + mutex_unlock(&desc->m); } diff --git a/firmware/target/arm/imx31/gigabeat-s/i2c-imx31.h b/firmware/target/arm/imx31/gigabeat-s/i2c-imx31.h index c708ebbfb4..d6de5c47e8 100644 --- a/firmware/target/arm/imx31/gigabeat-s/i2c-imx31.h +++ b/firmware/target/arm/imx31/gigabeat-s/i2c-imx31.h @@ -16,7 +16,61 @@ * KIND, either express or implied. * ****************************************************************************/ +#ifndef _I2C_IMX31_H_ +#define _I2C_IMX31_H_ + +#include + +/* I2C module usage masks */ +#define USE_I2C1_MODULE (1 << 0) +#define USE_I2C2_MODULE (1 << 1) +#define USE_I2C3_MODULE (1 << 2) + +enum i2c_module_number +{ + __I2C_NUM_START = -1, +#if (I2C_MODULE_MASK & USE_I2C1_MODULE) + I2C1_NUM, +#endif +#if (I2C_MODULE_MASK & USE_I2C2_MODULE) + I2C2_NUM, +#endif +#if (I2C_MODULE_MASK & USE_I2C3_MODULE) + I2C3_NUM, +#endif + I2C_NUM_I2C, +}; + +/* Module interface map structure */ +struct i2c_map +{ + volatile uint16_t iadr; /* 0x00 */ + volatile uint16_t unused1; + volatile uint16_t ifdr; /* 0x04 */ + volatile uint16_t unused2; + volatile uint16_t i2cr; /* 0x08 */ + volatile uint16_t unused3; + volatile uint16_t i2sr; /* 0x0C */ + volatile uint16_t unused4; + volatile uint16_t i2dr; /* 0x10 */ +}; + +struct i2c_node +{ + enum i2c_module_number num; /* Module that this node uses */ + unsigned int ifdr; /* Maximum frequency for node */ + unsigned char addr; /* Slave address on module */ +}; void i2c_init(void); -void i2c_write(int addr, const unsigned char *data, int count); +/* Enable or disable the node - modules will be switch on/off accordingly. */ +void i2c_enable_node(struct i2c_node *node, bool enable); +/* If addr < 0, then raw read */ +int i2c_read(struct i2c_node *node, int addr, unsigned char *data, int count); +int i2c_write(struct i2c_node *node, const unsigned char *data, int count); +/* Gain mutually-exclusive access to the node and module to perform multiple + * operations atomically */ +void i2c_lock_node(struct i2c_node *node); +void i2c_unlock_node(struct i2c_node *node); +#endif /* _I2C_IMX31_H_ */ diff --git a/firmware/target/arm/imx31/gigabeat-s/pcm-imx31.c b/firmware/target/arm/imx31/gigabeat-s/pcm-imx31.c index 4d2206bd56..e37f6bfbe2 100644 --- a/firmware/target/arm/imx31/gigabeat-s/pcm-imx31.c +++ b/firmware/target/arm/imx31/gigabeat-s/pcm-imx31.c @@ -49,10 +49,12 @@ void pcm_apply_settings(void) void pcm_play_dma_init(void) { + audiohw_init(); } void pcm_postinit(void) { + audiohw_postinit(); } #if 0 diff --git a/firmware/target/arm/imx31/gigabeat-s/wmcodec-imx31.c b/firmware/target/arm/imx31/gigabeat-s/wmcodec-imx31.c index 238ee3aeb2..235ae54bad 100644 --- a/firmware/target/arm/imx31/gigabeat-s/wmcodec-imx31.c +++ b/firmware/target/arm/imx31/gigabeat-s/wmcodec-imx31.c @@ -7,7 +7,7 @@ * \/ \/ \/ \/ \/ * $Id$ * - * Gigabeat specific code for the Wolfson codec + * Gigabeat S specific code for the WM8978 codec * * Based on code from the ipodlinux project - http://ipodlinux.org/ * Adapted for Rockbox in December 2005 @@ -28,12 +28,31 @@ #include "sound.h" #include "i2c-imx31.h" +/* NOTE: Some port-specific bits will have to be moved away (node and GPIO + * writes) for cleanest implementation. */ + +static struct i2c_node wm8978_i2c_node = +{ + .num = I2C1_NUM, + .ifdr = I2C_IFDR_DIV192, /* 66MHz/.4MHz = 165, closest = 192 = 343750Hz */ + /* Just hard-code for now - scaling may require + * updating */ + .addr = WM8978_I2C_ADDR, +}; + void audiohw_init(void) { + i2c_enable_node(&wm8978_i2c_node, true); + GPIO3_DR |= (1 << 21); /* Turn on analogue LDO */ + sleep(HZ/10); /* Wait for things to stabilize */ + audiohw_preinit(); } void wmcodec_write(int reg, int data) { - (void)reg; - (void)data; + unsigned char d[2]; + /* |aaaaaaad|dddddddd| */ + d[0] = (reg << 1) | ((data & 0x100) >> 8); + d[1] = data; + i2c_write(&wm8978_i2c_node, d, 2); } -- cgit v1.2.3