/*
 * Copyright © 2012 Keith Packard <keithp@keithp.com>
 *
 * 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 program is distributed in the hope that it will be useful, but
 * WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License along
 * with this program; if not, write to the Free Software Foundation, Inc.,
 * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.
 */

#include <ao.h>

struct ao_i2c_stm_info {
	uint8_t	tx_dma_index;
	uint8_t	rx_dma_index;
	struct stm_i2c	*stm_i2c;
};

#define I2C_FAST	1

#define I2C_TIMEOUT	100

#define I2C_IDLE	0
#define I2C_RUNNING	1
#define I2C_ERROR	2

static uint8_t	ao_i2c_state[STM_NUM_I2C];
static uint16_t	ao_i2c_addr[STM_NUM_I2C];
uint8_t 	ao_i2c_mutex[STM_NUM_I2C];

# define I2C_HIGH_SLOW	5000	/* ns, 100kHz clock */
#ifdef TELEMEGA
# define I2C_HIGH_FAST	2000	/* ns, 167kHz clock */
#else
# define I2C_HIGH_FAST	1000	/* ns, 333kHz clock */
#endif

# define I2C_RISE_SLOW	500	/* ns */
# define I2C_RISE_FAST	100	/* ns */

/* Clock period in ns */
#define CYCLES(period)	(((period) * (AO_PCLK1 / 1000)) / 1000000)

#define max(a,b)	((a) > (b) ? (a) : (b))
#define I2C_CCR_HIGH_SLOW	max(4,CYCLES(I2C_HIGH_SLOW))
#define I2C_CCR_HIGH_FAST	max(4,CYCLES(I2C_HIGH_FAST))
#define I2C_TRISE_SLOW		(CYCLES(I2C_RISE_SLOW) + 1)
#define I2C_TRISE_FAST		(CYCLES(I2C_RISE_FAST) + 1)

#if I2C_FAST
#define I2C_TRISE	I2C_TRISE_FAST
#define I2C_CCR_HIGH	I2C_CCR_HIGH_FAST
#else
#define I2C_TRISE	I2C_TRISE_SLOW
#define I2C_CCR_HIGH	I2C_CCR_HIGH_SLOW
#endif

#define AO_STM_I2C_CR2_FREQ	(AO_APB1CLK / 1000000)

#define AO_STM_I2C_CR1 ((0 << STM_I2C_CR1_SWRST) |	\
			(0 << STM_I2C_CR1_ALERT) |	\
			(0 << STM_I2C_CR1_PEC) |	\
			(0 << STM_I2C_CR1_POS) |	\
			(0 << STM_I2C_CR1_ACK) |	\
			(0 << STM_I2C_CR1_STOP) |	\
			(0 << STM_I2C_CR1_START) |	\
			(0 << STM_I2C_CR1_NOSTRETCH) |	\
			(0 << STM_I2C_CR1_ENGC) |	\
			(0 << STM_I2C_CR1_ENPEC) |	\
			(0 << STM_I2C_CR1_ENARP) |	\
			(0 << STM_I2C_CR1_SMBTYPE) |	\
			(0 << STM_I2C_CR1_SMBUS) |	\
			(1 << STM_I2C_CR1_PE))

#define AO_STM_I2C_CR2  ((0 << STM_I2C_CR2_LAST) |			\
			 (0 << STM_I2C_CR2_DMAEN) |			\
			 (0 << STM_I2C_CR2_ITBUFEN) |			\
			 (0 << STM_I2C_CR2_ITEVTEN) |			\
			 (0 << STM_I2C_CR2_ITERREN) |			\
			 (AO_STM_I2C_CR2_FREQ << STM_I2C_CR2_FREQ))

static const struct ao_i2c_stm_info	ao_i2c_stm_info[STM_NUM_I2C] = {
	{
		.tx_dma_index = STM_DMA_INDEX(STM_DMA_CHANNEL_I2C1_TX),
		.rx_dma_index = STM_DMA_INDEX(STM_DMA_CHANNEL_I2C1_RX),
		.stm_i2c = &stm_i2c1
	},
	{
		.tx_dma_index = STM_DMA_INDEX(STM_DMA_CHANNEL_I2C2_TX),
		.rx_dma_index = STM_DMA_INDEX(STM_DMA_CHANNEL_I2C2_RX),
		.stm_i2c = &stm_i2c2
	},
};

static uint8_t	*ao_i2c_recv_data[STM_NUM_I2C];
static uint16_t	ao_i2c_recv_len[STM_NUM_I2C];
static uint16_t	ev_count;

static void
ao_i2c_ev_isr(uint8_t index)
{
	struct stm_i2c	*stm_i2c = ao_i2c_stm_info[index].stm_i2c;
	uint32_t	sr1;

	++ev_count;
	sr1 = stm_i2c->sr1;
	if (sr1 & (1 << STM_I2C_SR1_SB))
		stm_i2c->dr = ao_i2c_addr[index];
	if (sr1 & (1 << STM_I2C_SR1_ADDR)) {
		stm_i2c->cr2 &= ~(1UL << STM_I2C_CR2_ITEVTEN);
		ao_i2c_state[index] = I2C_RUNNING;
		ao_wakeup(&ao_i2c_state[index]);
	}
	if (sr1 & (1 << STM_I2C_SR1_BTF)) {
		stm_i2c->cr2 &= ~(1UL << STM_I2C_CR2_ITEVTEN);
		ao_wakeup(&ao_i2c_state[index]);
	}
	if (sr1 & (1 << STM_I2C_SR1_RXNE)) {
		if (ao_i2c_recv_len[index]) {
			*(ao_i2c_recv_data[index]++) = (uint8_t) stm_i2c->dr;
			if (!--ao_i2c_recv_len[index])
				ao_wakeup(&ao_i2c_recv_len[index]);
		}
	}
}

void stm_i2c1_ev_isr(void) { ao_i2c_ev_isr(0); }
void stm_i2c2_ev_isr(void) { ao_i2c_ev_isr(1); }

static void
ao_i2c_er_isr(uint8_t index)
{
	struct stm_i2c	*stm_i2c = ao_i2c_stm_info[index].stm_i2c;
	uint32_t	sr1;

	sr1 = stm_i2c->sr1;
	if (sr1 & (1 << STM_I2C_SR1_AF)) {
		ao_i2c_state[index] = I2C_ERROR;
		stm_i2c->sr1 = sr1 & ~(1UL << STM_I2C_SR1_AF);
		ao_wakeup(&ao_i2c_state[index]);
	}
}

void stm_i2c1_er_isr(void) { ao_i2c_er_isr(0); }
void stm_i2c2_er_isr(void) { ao_i2c_er_isr(1); }

void
ao_i2c_get(uint8_t index)
{
	struct stm_i2c	*stm_i2c = ao_i2c_stm_info[index].stm_i2c;
	ao_mutex_get(&ao_i2c_mutex[index]);

	stm_i2c->sr1 = 0;
	stm_i2c->sr2 = 0;
}

void
ao_i2c_put(uint8_t index)
{
	ao_mutex_put(&ao_i2c_mutex[index]);
}

uint8_t
ao_i2c_start(uint8_t index, uint16_t addr)
{
	struct stm_i2c	*stm_i2c = ao_i2c_stm_info[index].stm_i2c;
	int		t;

	ao_i2c_state[index] = I2C_IDLE;
	ao_i2c_addr[index] = addr;
	stm_i2c->cr2 = AO_STM_I2C_CR2;
	stm_i2c->cr1 = AO_STM_I2C_CR1 | (1 << STM_I2C_CR1_START);
	for (t = 0; t < I2C_TIMEOUT; t++) {
		if (!(stm_i2c->cr1 & (1 << STM_I2C_CR1_START)))
			break;
	}
	ao_arch_block_interrupts();
	stm_i2c->cr2 = AO_STM_I2C_CR2 | (1 << STM_I2C_CR2_ITEVTEN) | (1 << STM_I2C_CR2_ITERREN);
	ao_i2c_ev_isr(index);
	while (ao_i2c_state[index] == I2C_IDLE)
		if (ao_sleep_for(&ao_i2c_state[index], AO_MS_TO_TICKS(250)))
			break;
	ao_arch_release_interrupts();
	return ao_i2c_state[index] == I2C_RUNNING;
}

static void
ao_i2c_wait_stop(uint8_t index)
{
	struct stm_i2c	*stm_i2c = ao_i2c_stm_info[index].stm_i2c;
	int	t;

	for (t = 0; t < I2C_TIMEOUT; t++) {
		if (!(stm_i2c->cr1 & (1 << STM_I2C_CR1_STOP)))
			break;
		ao_yield();
	}
	ao_i2c_state[index] = I2C_IDLE;
}

static void
ao_i2c_wait_addr(uint8_t index)
{
	struct stm_i2c	*stm_i2c = ao_i2c_stm_info[index].stm_i2c;
	int	t;

	for (t = 0; t < I2C_TIMEOUT; t++)
		if (!(stm_i2c->sr1 & (1 << STM_I2C_SR1_ADDR)))
			break;
	if (t)
		printf ("wait_addr %d\n", t);
}

uint8_t
ao_i2c_send(void *block, uint16_t len, uint8_t index, uint8_t stop)
{
	struct stm_i2c	*stm_i2c = ao_i2c_stm_info[index].stm_i2c;
	uint8_t		tx_dma_index = ao_i2c_stm_info[index].tx_dma_index;
	uint8_t		rx_dma_index = ao_i2c_stm_info[index].rx_dma_index;

	/* Clear any pending ADDR bit */
	(void) stm_i2c->sr2;
	ao_i2c_wait_addr(index);
	ao_dma_set_transfer(tx_dma_index,
			    &stm_i2c->dr,
			    block,
			    len,
			    (0 << STM_DMA_CCR_MEM2MEM) |
			    (STM_DMA_CCR_PL_MEDIUM << STM_DMA_CCR_PL) |
			    (STM_DMA_CCR_MSIZE_8 << STM_DMA_CCR_MSIZE) |
			    (STM_DMA_CCR_PSIZE_8 << STM_DMA_CCR_PSIZE) |
			    (1 << STM_DMA_CCR_MINC) |
			    (0 << STM_DMA_CCR_PINC) |
			    (0 << STM_DMA_CCR_CIRC) |
			    (STM_DMA_CCR_DIR_MEM_TO_PER << STM_DMA_CCR_DIR));
	ao_dma_mutex_get(rx_dma_index);
	stm_i2c->cr2 = AO_STM_I2C_CR2 | (1 << STM_I2C_CR2_DMAEN);

	ao_dma_start(tx_dma_index);
	ao_arch_block_interrupts();
	while (!ao_dma_done[tx_dma_index])
		if (ao_sleep_for(&ao_dma_done[tx_dma_index], 1 + len))
			break;
	stm_i2c->cr2 = AO_STM_I2C_CR2 | (1 << STM_I2C_CR2_ITEVTEN) | (1 << STM_I2C_CR2_ITERREN);
	while ((stm_i2c->sr1 & (1 << STM_I2C_SR1_BTF)) == 0)
		if (ao_sleep_for(&ao_i2c_state[index], 1 + len))
			break;
	stm_i2c->cr2 = AO_STM_I2C_CR2;
	ao_arch_release_interrupts();
	if (stop) {
		stm_i2c->cr1 = AO_STM_I2C_CR1 | (1 << STM_I2C_CR1_STOP);
		ao_i2c_wait_stop(index);
	}
	ao_dma_mutex_put(rx_dma_index);
	ao_dma_done_transfer(tx_dma_index);
	return true;
}

static void
ao_i2c_recv_dma_isr(int index)
{
	int		i;
	struct stm_i2c	*stm_i2c = NULL;

	for (i = 0; i < STM_NUM_I2C; i++)
		if (index == ao_i2c_stm_info[i].rx_dma_index) {
			stm_i2c = ao_i2c_stm_info[i].stm_i2c;
			break;
		}
	if (!stm_i2c)
		return;
	stm_i2c->cr2 = AO_STM_I2C_CR2 | (1 << STM_I2C_CR2_LAST);
	ao_dma_done[index] = 1;
	ao_wakeup(&ao_dma_done[index]);
}

uint8_t
ao_i2c_recv(void *block, uint16_t len, uint8_t index, uint8_t stop)
{
	struct stm_i2c	*stm_i2c = ao_i2c_stm_info[index].stm_i2c;
	uint8_t		ret = true;

	if (len == 0)
		return true;
	if (len == 1) {
		ao_i2c_recv_data[index] = block;
		ao_i2c_recv_len[index] = 1;
		stm_i2c->cr1 = AO_STM_I2C_CR1;

		/* Clear any pending ADDR bit */
		stm_i2c->sr2;
		ao_i2c_wait_addr(index);

		/* Enable interrupts to transfer the byte */
		stm_i2c->cr2 = (AO_STM_I2C_CR2 |
				(1 << STM_I2C_CR2_ITEVTEN) |
				(1 << STM_I2C_CR2_ITERREN) |
				(1 << STM_I2C_CR2_ITBUFEN));
		if (stop)
			stm_i2c->cr1 = AO_STM_I2C_CR1 | (1 << STM_I2C_CR1_STOP);

		ao_arch_block_interrupts();
		while (ao_i2c_recv_len[index])
			if (ao_sleep_for(&ao_i2c_recv_len[index], 1))
				break;
		ao_arch_release_interrupts();
		ret = ao_i2c_recv_len[index] == 0;
	} else {
		uint8_t		tx_dma_index = ao_i2c_stm_info[index].tx_dma_index;
		uint8_t		rx_dma_index = ao_i2c_stm_info[index].rx_dma_index;
		ao_dma_mutex_get(tx_dma_index);
		ao_dma_set_transfer(rx_dma_index,
				    &stm_i2c->dr,
				    block,
				    len,
				    (0 << STM_DMA_CCR_MEM2MEM) |
				    (STM_DMA_CCR_PL_HIGH << STM_DMA_CCR_PL) |
				    (STM_DMA_CCR_MSIZE_8 << STM_DMA_CCR_MSIZE) |
				    (STM_DMA_CCR_PSIZE_8 << STM_DMA_CCR_PSIZE) |
				    (1 << STM_DMA_CCR_MINC) |
				    (0 << STM_DMA_CCR_PINC) |
				    (0 << STM_DMA_CCR_CIRC) |
				    (STM_DMA_CCR_DIR_PER_TO_MEM << STM_DMA_CCR_DIR));

		/* XXX ao_i2c_recv_dma_isr hasn't ever been used, so it
		 * doesn't appear to be necessary. Testing with a device
		 * that uses i2c would really be useful here to discover
		 * whether this function is necessary or not.
		 */
#if 0
		ao_dma_set_isr(rx_dma_index, ao_i2c_recv_dma_isr);
#else
		(void) ao_i2c_recv_dma_isr;
#endif
		stm_i2c->cr1 = AO_STM_I2C_CR1 | (1 << STM_I2C_CR1_ACK);
		stm_i2c->cr2 = AO_STM_I2C_CR2 |
			(1 << STM_I2C_CR2_DMAEN) | (1 << STM_I2C_CR2_LAST);
		/* Clear any pending ADDR bit */
		(void) stm_i2c->sr2;
		ao_i2c_wait_addr(index);

		ao_dma_start(rx_dma_index);
		ao_arch_block_interrupts();
		while (!ao_dma_done[rx_dma_index])
			if (ao_sleep_for(&ao_dma_done[rx_dma_index], len))
				break;
		ao_arch_release_interrupts();
		ret = ao_dma_done[rx_dma_index];
		stm_i2c->cr1 = AO_STM_I2C_CR1 | (1 << STM_I2C_CR1_STOP);
		ao_dma_done_transfer(rx_dma_index);
		ao_dma_mutex_put(tx_dma_index);
	}
	if (stop)
		ao_i2c_wait_stop(index);
	return ret;
}

static void
ao_i2c_channel_init(uint8_t index)
{
	struct stm_i2c	*stm_i2c = ao_i2c_stm_info[index].stm_i2c;
	int i;

	/* Turn I2C off while configuring */
	stm_i2c->cr1 = (1 << STM_I2C_CR1_SWRST);
	for (i = 0; i < 100; i++)
		asm("nop");
	stm_i2c->cr1 = 0;
	stm_i2c->cr2 = AO_STM_I2C_CR2;

	(void) stm_i2c->sr1;
	(void) stm_i2c->sr2;
	(void) stm_i2c->dr;

	stm_i2c->sr1 = 0;
	stm_i2c->sr2 = 0;

	stm_i2c->ccr = ((I2C_FAST << STM_I2C_CCR_FS) |
			(0 << STM_I2C_CCR_DUTY) |
			(I2C_CCR_HIGH << STM_I2C_CCR_CCR));

	stm_i2c->trise = I2C_TRISE;

	stm_i2c->cr1 = AO_STM_I2C_CR1;
}

static inline void
i2c_pin_set(struct stm_gpio *gpio, int pin)
{
	ao_enable_port(gpio);
	stm_gpio_conf(gpio, pin,
		      STM_GPIO_CR_MODE_OUTPUT_2MHZ,
		      STM_GPIO_CR_CNF_OUTPUT_AF_OPEN_DRAIN);
}

void
ao_i2c_init(void)
{
#if HAS_I2C_1
# if I2C_1_PB6_PB7
	stm_set_afio_mapr(STM_AFIO_MAPR_I2C1_REMAP,
			  STM_AFIO_MAPR_I2C1_REMAP_PB6_PB7,
			  STM_AFIO_MAPR_I2C1_REMAP_MASK);
	i2c_pin_set(&stm_gpiob, 6);
	i2c_pin_set(&stm_gpiob, 7);
# else
#  if I2C_1_PB8_PB9
	stm_set_afio_mapr(STM_AFIO_MAPR_I2C1_REMAP,
			  STM_AFIO_MAPR_I2C1_REMAP_PB8_PB9,
			  STM_AFIO_MAPR_I2C1_REMAP_MASK);
	i2c_pin_set(&stm_gpiob, 8);
	i2c_pin_set(&stm_gpiob, 9);
#  else
#   error "No I2C_1 port configuration specified"
#  endif
# endif

	stm_rcc.apb1enr |= (1 << STM_RCC_APB1ENR_I2C1EN);
	ao_i2c_channel_init(0);

	stm_nvic_set_enable(STM_ISR_I2C1_EV_POS);
	stm_nvic_set_priority(STM_ISR_I2C1_EV_POS, AO_STM_NVIC_MED_PRIORITY);
	stm_nvic_set_enable(STM_ISR_I2C1_ER_POS);
	stm_nvic_set_priority(STM_ISR_I2C1_ER_POS, AO_STM_NVIC_MED_PRIORITY);
#endif

#if HAS_I2C_2
# if I2C_2_PB10_PB11
	i2c_pin_set(&stm_gpiob, 10);
	i2c_pin_set(&stm_gpiob, 11);
# else
#  error "No I2C_2 port configuration specified"
# endif
	stm_rcc.apb1enr |= (1 << STM_RCC_APB1ENR_I2C2EN);
	ao_i2c_channel_init(1);

	stm_nvic_set_enable(STM_ISR_I2C2_EV_POS);
	stm_nvic_set_priority(STM_ISR_I2C2_EV_POS, AO_STM_NVIC_MED_PRIORITY);
	stm_nvic_set_enable(STM_ISR_I2C2_ER_POS);
	stm_nvic_set_priority(STM_ISR_I2C2_ER_POS, AO_STM_NVIC_MED_PRIORITY);
#endif
}
