/*
 * linux/net/driver/wireless/cx3110x/sm_drv_spi_io.c
 *
 * Nokia 770 SPI wrapper for Conexant Softmac driver.
 *
 * Copyright (C) 2004,2005 Nokia Corporation
 * Author: Samuel Ortiz <samuel.ortiz@nokia.com>
 *
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License version 2 as
 * published by the Free Software Foundation.
 *
 */
#include <linux/init.h>
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/dma-mapping.h>

#include <asm/delay.h>
#include <asm/string.h>
#include <asm/irq.h>

#include <asm/hardware/clock.h>
#include <asm/arch/gpio.h>
#include <asm/arch/mcbsp.h>
#include <asm/arch/dma.h>
#include <asm/arch/board-nokia.h>

#include "islspi_init.h"
#undef FREE
#include "isl_sm.h"

#define SPI_CS_GPIO 21
#define SPI_CS_ON() omap_set_gpio_dataout(SPI_CS_GPIO, 0)
#define SPI_CS_OFF() omap_set_gpio_dataout(SPI_CS_GPIO,1)

#define WLAN_POWER_DOWN(power_gpio) omap_set_gpio_dataout((power_gpio), 0);

#define ADDR_READ_BIT_15                0x8000  // Bit 15 is read/write bit; ON = READ, OFF = WRITE

/* I want to be as source-compatible as possible for this file.
   So there. */
#define DEBUG(x,args...)

struct omap_wlan_spi_dma {
	int dma_tx_ch;
	int dma_rx_ch;

	int dma_tx_done;
	int dma_rx_done;

	uint16_t recv_buffer;
};

static struct omap_wlan_spi_dma spi_dma;

static void omap_wlan_hw_reset(uint16_t power_gpio)
{
	omap_set_gpio_dataout(power_gpio, 0);
	/* Scope measured timing for 1 ms...*/
	udelay(1300);
	omap_set_gpio_dataout(power_gpio, 1);
	mdelay(100);
}

int omap_wlan_request_irq(irqreturn_t (*handler)(int, void *, struct pt_regs *),
			  const char * devname, void *dev_id)
{
	struct spi_hif_local_data *spi_lp = HIF_OF_NETDEV(dev_id);
	const struct omap_wlan_config * wlan_config;
	int ret;

	if (spi_lp->nokia_wlan_config == NULL)
		return -1;

	wlan_config = spi_lp->nokia_wlan_config;

	ret = request_irq(OMAP_GPIO_IRQ(wlan_config->irq_gpio),
			  handler, SA_INTERRUPT , devname, dev_id);

	if (ret < 0) {
		printk(KERN_WARNING "%s could not install IRQ-handler %d\n", devname, ret);
		return ret;
	}

	return 0;
}

void omap_wlan_free_irq(void *dev_id)
{
	struct spi_hif_local_data *spi_lp;
	const struct omap_wlan_config * wlan_config;

	spi_lp = HIF_OF_NETDEV(dev_id);

	if (spi_lp->nokia_wlan_config == NULL)
		return;

	wlan_config = spi_lp->nokia_wlan_config;

	free_irq(OMAP_GPIO_IRQ(wlan_config->irq_gpio), dev_id);
}

void omap_wlan_disable_irq(struct net_device *dev)
{
	struct spi_hif_local_data *spi_lp;
	const struct omap_wlan_config * wlan_config;

	DEBUG(DBG_CALLS, "omap_wlan_disable_irq\n");

	spi_lp = HIF_OF_NETDEV(dev);

	if (spi_lp->nokia_wlan_config == NULL)
		return;

	wlan_config = spi_lp->nokia_wlan_config;

	disable_irq(OMAP_GPIO_IRQ(wlan_config->irq_gpio));
}


/* SPI DMA operations */
static void dma_tx_callback(int lch, u16 ch_status, void *data)
{
	if (ch_status & OMAP_DMA_TOUT_IRQ)
		printk("McBSP TX DMA timeout\n");
	if (ch_status & OMAP_DMA_DROP_IRQ)
		printk("McBSP TX DMA drop\n");
	spi_dma.dma_tx_done = 1;
}

static void dma_rx_callback(int lch, u16 ch_status, void *data)
{
	if (ch_status & OMAP_DMA_TOUT_IRQ)
		printk("McBSP RX DMA timeout\n");
	if (ch_status & OMAP_DMA_DROP_IRQ)
		printk("McBSP RX DMA drop\n");
	spi_dma.dma_rx_done = 1;
}


int omap_wlan_spi_dma_read(unsigned long address, void * buffer, unsigned int length)
{
	SPI_CS_ON();

	omap_mcbsp_spi_master_xmit_word_poll(OMAP_MCBSP2,
				      (address << 8) | ADDR_READ_BIT_15);

	/* Start writing */
	omap_set_dma_transfer_params(spi_dma.dma_tx_ch,
				     OMAP_DMA_DATA_TYPE_S16,
				     length >> 1, 1,
				     OMAP_DMA_SYNC_ELEMENT);

	omap_set_dma_dest_params(spi_dma.dma_tx_ch,
				 OMAP_DMA_PORT_TIPB,
				 OMAP_DMA_AMODE_CONSTANT,
				 OMAP1610_MCBSP2_BASE + OMAP_MCBSP_REG_DXR1);

	omap_set_dma_src_params(spi_dma.dma_tx_ch,
				OMAP_DMA_PORT_EMIFF,
				OMAP_DMA_AMODE_CONSTANT,
				virt_to_phys(&spi_dma.recv_buffer));

	/* Prepare for reading */
	omap_set_dma_transfer_params(spi_dma.dma_rx_ch,
				     OMAP_DMA_DATA_TYPE_S16,
				     length >> 1, 1,
				     OMAP_DMA_SYNC_ELEMENT);

	omap_set_dma_src_params(spi_dma.dma_rx_ch,
				OMAP_DMA_PORT_TIPB,
				OMAP_DMA_AMODE_CONSTANT,
				OMAP1610_MCBSP2_BASE + OMAP_MCBSP_REG_DRR1);

	omap_set_dma_dest_params(spi_dma.dma_rx_ch,
				 OMAP_DMA_PORT_EMIFF,
				 OMAP_DMA_AMODE_POST_INC,
				 virt_to_phys(buffer));

	/* DMA doesn't use CPU cache */
	flush_cache_all();

	/* We start writing first, for the clock */
	omap_start_dma(spi_dma.dma_rx_ch);
	omap_start_dma(spi_dma.dma_tx_ch);

	/* Wait for reading to complete */
	while(!spi_dma.dma_rx_done) {
		udelay(5);
	}

	while(!spi_dma.dma_tx_done) {
		udelay(5);
	}

	spi_dma.dma_rx_done = 0;
	spi_dma.dma_tx_done = 0;

	SPI_CS_OFF();

	return 0;
}

int omap_wlan_spi_dma_write(unsigned long address, void * buffer, unsigned int length)
{
	SPI_CS_ON();

	omap_mcbsp_spi_master_xmit_word_poll(OMAP_MCBSP2, address << 8);

	/* Prepare reading */
	omap_set_dma_transfer_params(spi_dma.dma_rx_ch,
				     OMAP_DMA_DATA_TYPE_S16,
				     length >> 1, 1,
				     OMAP_DMA_SYNC_ELEMENT);

	omap_set_dma_src_params(spi_dma.dma_rx_ch,
				OMAP_DMA_PORT_TIPB,
				OMAP_DMA_AMODE_CONSTANT,
				OMAP1610_MCBSP2_BASE + OMAP_MCBSP_REG_DRR1);

	omap_set_dma_dest_params(spi_dma.dma_rx_ch,
				 OMAP_DMA_PORT_EMIFF,
				 OMAP_DMA_AMODE_CONSTANT,
				 virt_to_phys(&spi_dma.recv_buffer));



	/* Prepare writing */
	omap_set_dma_transfer_params(spi_dma.dma_tx_ch,
				     OMAP_DMA_DATA_TYPE_S16,
				     length >> 1, 1,
				     OMAP_DMA_SYNC_ELEMENT);

	omap_set_dma_dest_params(spi_dma.dma_tx_ch,
				 OMAP_DMA_PORT_TIPB,
				 OMAP_DMA_AMODE_CONSTANT,
				 OMAP1610_MCBSP2_BASE + OMAP_MCBSP_REG_DXR1);

	omap_set_dma_src_params(spi_dma.dma_tx_ch,
				OMAP_DMA_PORT_EMIFF,
				OMAP_DMA_AMODE_POST_INC,
				virt_to_phys(buffer));


	/* DMA doesn't use CPU cache */
	flush_cache_all();

	/* RX first, and then TX */
	omap_start_dma(spi_dma.dma_rx_ch);
	omap_start_dma(spi_dma.dma_tx_ch);

	/* We don't want to turn CS off before transfer is done */

	while(!spi_dma.dma_tx_done) {
		udelay(5);
	}

	spi_dma.dma_tx_done = 0;

	/* UMAC may send us odd number of bytes long frames */
	if (length % 2) {
		u16 last_word;

		last_word = *(uint16_t *)(buffer + length - 1);
		omap_mcbsp_spi_master_xmit_word_poll(OMAP_MCBSP2, last_word);
	}

	SPI_CS_OFF();

	return 0;
}

int omap_wlan_spi_read(unsigned long address, unsigned char * buffer, unsigned int length)
{
	int i;
	u16 * short_buffer = (u16 *) buffer;
	unsigned int r_length = length >> 1;

	DEBUG(DBG_SPI_IO, "omap_wlan_spi_read\n");

	SPI_CS_ON();

	omap_mcbsp_spi_master_xmit_word_poll(OMAP_MCBSP2,
				      (address << 8) | ADDR_READ_BIT_15);

	for (i = 0 ; i < r_length ; i++)
		omap_mcbsp_spi_master_recv_word_poll(OMAP_MCBSP2, (u32*)(short_buffer + i));

	SPI_CS_OFF();
	return 0;
}

int omap_wlan_spi_write(unsigned long address, unsigned char * buffer, unsigned int length)
{
	int i;
	u16 * short_buffer = (u16 *) buffer;
	unsigned int w_length = length >> 1;


	DEBUG(DBG_SPI_IO, "omap_wlan_spi_write (%d bytes @ 0x%lx)\n", length, address << 8);

	SPI_CS_ON();

	omap_mcbsp_spi_master_xmit_word_poll(OMAP_MCBSP2, address << 8);

	for (i = 0 ; i < w_length ; i++)
		omap_mcbsp_spi_master_xmit_word_poll(OMAP_MCBSP2, short_buffer[i]);

	/* UMAC may send us odd number of bytes long frames */
	if (length % 2) {
		u16 last_word;

		last_word = buffer[length - 1];
		omap_mcbsp_spi_master_xmit_word_poll(OMAP_MCBSP2, last_word);
	}

	SPI_CS_OFF();

	return 0;
}

int omap_wlan_spi_probe(struct net_device *dev)
{
	struct omap_mcbsp_spi_cfg spi_cfg;
	int r, div = 1, rate_mhz, max_mhz = 14;
	struct spi_hif_local_data *spi_lp = HIF_OF_NETDEV(dev);
	const struct omap_wlan_config * wlan_config;

	wlan_config = spi_lp->nokia_wlan_config;

	if (!wlan_config)
		return -1;

	if (wlan_config->irq_gpio >= OMAP_MPUIO(0)) {
		printk("WLAN IRQ on MPUIO is not supported\n");
		return -EPERM;
	}

	rate_mhz = clk_get_rate(HIF_CLK(spi_lp))/1000000;

	DEBUG(DBG_CALLS, "omap_wlan_spi_probe at MCBSP rate: %d Mhz\n", rate_mhz);

	if (omap_request_gpio(SPI_CS_GPIO)) {
		printk(KERN_ERR "Cannot request GPIO %d\n", SPI_CS_GPIO);
		return -1;
	}

	if (omap_request_gpio(wlan_config->power_gpio)) {
		printk(KERN_ERR "Cannot request GPIO %d\n", wlan_config->power_gpio);
		return -1;
	}

	if (omap_request_gpio(wlan_config->irq_gpio)) {
		printk(KERN_ERR "Cannot request MPUIO %d\n", wlan_config->irq_gpio);
		return -1;
	}

	/* GPIO21 controls the SPI_CS line */
	omap_set_gpio_direction(SPI_CS_GPIO, 0);

	/* GPIO59 (output gpio) powers the wlan chip up */
	omap_set_gpio_direction(wlan_config->power_gpio, 0);

	/* MPUIO 3 controls the WLAN IRQ */
	omap_set_gpio_direction(wlan_config->irq_gpio, 1);
	set_irq_type(OMAP_GPIO_IRQ(wlan_config->irq_gpio), IRQT_RISING);

	omap_wlan_hw_reset(wlan_config->power_gpio);

	if (spi_lp->fast_mode)
		max_mhz = 48;

	while(rate_mhz/div >= max_mhz)
		div++;

//	printk("SPI bus running at %d Mhz\n", rate_mhz/div);

	spi_dma.dma_tx_done = 0;
	spi_dma.dma_rx_done = 0;

	if (omap_request_dma(OMAP_DMA_MCBSP2_TX, "McBSP TX", dma_tx_callback,
			     NULL, &spi_dma.dma_tx_ch)) {
		printk("Unable to request DMA channel for McBSP2 TX.\n");
		return -EAGAIN;
	}

	if (omap_request_dma(OMAP_DMA_MCBSP2_RX, "McBSP RX", dma_rx_callback,
			     NULL, &spi_dma.dma_rx_ch)) {
		printk("Unable to request DMA channel for McBSP2 TX.\n");
		return -EAGAIN;
	}

	spi_cfg.spi_mode = OMAP_MCBSP_SPI_MASTER;
	spi_cfg.rx_clock_polarity = OMAP_MCBSP_CLK_FALLING;
	spi_cfg.tx_clock_polarity = OMAP_MCBSP_CLK_RISING;
	spi_cfg.fsx_polarity = OMAP_MCBSP_FS_ACTIVE_LOW;
	spi_cfg.clk_div = div;
	spi_cfg.clk_stp_mode = OMAP_MCBSP_CLK_STP_MODE_DELAY;
	spi_cfg.word_length = OMAP_MCBSP_WORD_16;

	/* We will do polling */
	r = omap_mcbsp_set_io_type(OMAP_MCBSP2, OMAP_MCBSP_POLL_IO);
	if ( r < 0)
		return r;

	r = omap_mcbsp_request(OMAP_MCBSP2);
	if ( r < 0)
		return r;
	omap_mcbsp_set_spi_mode(OMAP_MCBSP2, &spi_cfg);
	omap_mcbsp_start(OMAP_MCBSP2);

	return 0;
}

void omap_wlan_spi_disconnect(struct net_device *dev)
{
	struct spi_hif_local_data * spi_lp;
	const struct omap_wlan_config * wlan_config;

	spi_lp = HIF_OF_NETDEV(dev);

	if (spi_lp->nokia_wlan_config == NULL)
		return;

	wlan_config = spi_lp->nokia_wlan_config;

	DEBUG(DBG_CALLS, "omap_wlan_spi_disconnect\n");

	WLAN_POWER_DOWN(wlan_config->power_gpio);
	omap_mcbsp_free(OMAP_MCBSP2);
	omap_mcbsp_stop(OMAP_MCBSP2);
	omap_free_gpio(SPI_CS_GPIO);
	omap_free_gpio(wlan_config->power_gpio);
	omap_free_gpio(wlan_config->irq_gpio);
	omap_free_dma(spi_dma.dma_tx_ch);
	omap_free_dma(spi_dma.dma_rx_ch);
}


typedef struct
{
	u16  address;
	u16  length;
	char    *name;
} wlan_reg;


static const wlan_reg wlan_registers_array[] =
{
	{ SPI_ADRS_ARM_INTERRUPTS,                32, "ARM_INT     "  },
	{ SPI_ADRS_ARM_INT_EN,         32, "ARM_INT_ENA "  },
	{ SPI_ADRS_HOST_INTERRUPTS,               32, "HOST_INT    "  },
	{ SPI_ADRS_HOST_INT_EN,        32, "HOST_INT_ENA"  },
	{ SPI_ADRS_HOST_INT_ACK,   32, "HOST_INT_ACK"  },
	{ SPI_ADRS_GEN_PURP_1,            32, "GP1_COMM    "  },
	{ SPI_ADRS_GEN_PURP_2,            32, "GP2_COMM    "  },
	{ SPI_ADRS_DEV_CTRL_STAT,        32, "DEV_CTRL_STA"  },
	{ SPI_ADRS_DMA_DATA,                     16, "DMA_DATA    "  },
	{ SPI_ADRS_DMA_WRITE_CTRL,            16, "DMA_WR_CTRL "  },
	{ SPI_ADRS_DMA_WRITE_LEN,             16, "DMA_WR_LEN  "  },
	{ SPI_ADRS_DMA_WRITE_BASE,               32, "DMA_WR_BASE "  },
	{ SPI_ADRS_DMA_READ_CTRL,             16, "DMA_RD_CTRL "  },
	{ SPI_ADRS_DMA_READ_LEN,              16, "DMA_RD_LEN  "  },
	{ SPI_ADRS_DMA_WRITE_BASE,                32, "DMA_RD_BASE "  }
};


#ifndef ELEMENTS
    #define ELEMENTS( a )    sizeof( a ) / sizeof( a[ 0 ] )
#endif




void omap_wlan_dump_register(void)
{
	u16 i;
	char * buffer;

	buffer = kmalloc(10, GFP_KERNEL);

	DEBUG(DBG_SPI_IO, "**** WLAN registers ****\n");
	for (i = 0; i < ELEMENTS(wlan_registers_array); i++) {
		omap_wlan_spi_read(wlan_registers_array[i].address, buffer, (wlan_registers_array[i].length) >> 3);
		if (wlan_registers_array[i].length == 32)
			printk("%s -> 0x%08x (32 bits)\n", wlan_registers_array[i].name, *(u32*)buffer);
		else
			printk("%s -> 0x%04x (16 bits)\n", wlan_registers_array[i].name, *(u16*)buffer);
	}
}
