diff options
Diffstat (limited to 'drivers/scp/scp-dev.c')
-rwxr-xr-x | drivers/scp/scp-dev.c | 654 |
1 files changed, 654 insertions, 0 deletions
diff --git a/drivers/scp/scp-dev.c b/drivers/scp/scp-dev.c new file mode 100755 index 00000000000..b8f286c0342 --- /dev/null +++ b/drivers/scp/scp-dev.c @@ -0,0 +1,654 @@ +/* + ************************************************************************** + * drivers/spi/scp-dev.c -- Serial Communications Port + * + * + * 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., 675 Mass Ave, Cambridge, MA 02139, USA. + * + * + * + * (C) Copyright IBM Corp. 2004 Christophe Lombard <christophe_lombard@fr.ibm.com> + * + * + * Usage: + * Transmit data to scp + * echo -n "..." > /proc/driver/scp_dev + * Example: Write and read the value 0x19 at the address 0x1EE + + echo "Status Read " + + echo -n "CSENA" > /proc/driver/scp_dev # Enable FRAM + echo -n "RDSR" > /proc/driver/scp_dev # RDSR: 0x05 to read FRAM status + echo -n "0" > /proc/driver/scp_dev + echo -n "RXDATA" > /proc/driver/scp_dev # read data + echo -n "CSDIS" > /proc/driver/scp_dev # Disable FRAM + + + #echo "write data " + + echo -n "CSENA" > /proc/driver/scp_dev # Enable FRAM + echo -n "WREN" > /proc/driver/scp_dev # WREN: 0x06 + echo -n "CSDIS" > /proc/driver/scp_dev # Disable FRAM + + echo -n "CSENA" > /proc/driver/scp_dev # Enable FRAM + echo -n "WRITE" > /proc/driver/scp_dev # WRITE: 0x02 + echo -n "01" > /proc/driver/scp_dev # write address on 10 bits. Ex: 0x17EE (max : 3FF) + echo -n "EE" > /proc/driver/scp_dev + echo -n "19" > /proc/driver/scp_dev # write data on 8 bits. Ex: 19 + echo -n "CSDIS" > /proc/driver/scp_dev # Disable FRAM + + + #echo "read data " + + echo -n "CSENA" > /proc/driver/scp_dev # Enable FRAM + echo -n "READ" > /proc/driver/scp_dev # READ: 0x03 + echo -n "1" > /proc/driver/scp_dev # read address on 10 bits. Ex: 0x1EE + echo -n "EE" > /proc/driver/scp_dev + echo -n "0" > /proc/driver/scp_dev # write dummy data: 0 (Mandatory) + echo -n "RXDATA" > /proc/driver/scp_dev # read data + echo -n "CSDIS" > /proc/driver/scp_dev # Disable FRAM + * + * It is impotant to know the example to know how the SCP works. + * On Beech board the SPI EEPROM is AT25080A. This affects the max address. + * ************************************************************************/ +#include <linux/module.h> +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/init.h> +#include <linux/proc_fs.h> +#include <asm/uaccess.h> +#include <asm/io.h> +#include <linux/delay.h> +#include <linux/interrupt.h> +#include <asm/irq.h> +#include <linux/platform_device.h> +#include <asm/dcr-native.h> +#include <linux/of_platform.h> + +#undef SCP_DEBUG +#define SCP_DEBUG +#include "scp-dev.h" + +/* + * If you want debugging uncomment: + */ +#ifdef SCP_DEBUG +int scp_verbose = 1; +#endif + +#define DRIVER_VERSION "0.1" + +MODULE_DESCRIPTION("AMCC scp driver v" DRIVER_VERSION); +MODULE_LICENSE("GPL"); +static int major; /* default is dynamic major device number */ +module_param(major, int, 0); +MODULE_PARM_DESC(major, "Major device number"); + +#define MASK_SEL_SCP 0xFFFDFFFF +#define CS_LOW 0xFFFDFFFF +#define CS_HIGH 0x00020000 +#define GPIO_MASK 0xFFFFFFF3 + +static int +scp_ioctl(struct inode *inode, struct file *filep, unsigned int cmd, + unsigned long arg) +{ + return 0; +} + +static struct file_operations scp_device_fops = { + owner:THIS_MODULE, + ioctl:scp_ioctl, +}; + +static int scpdev_initialized; +static void *virtual_scp_add; /* Virtual address to access to the scp core */ +static void *gpio_base; + + +#define scp_readb(addr) in_8((volatile u8 *)(virtual_scp_add + (addr))) +#define scp_writeb(addr,b) out_8((volatile u8 *)(virtual_scp_add + (addr)), (b)) + +//static DECLARE_WAIT_QUEUE_HEAD(scp_wait); + +/* *********************************************************************** + * FUNCTIONS + * + */ + +/* + ************************************************************************** + * scp_set_configuration + * + * Change the configuration of the SCP. + * Enable the serial port and the serial clock Phase + * + * Return nothing. + * ************************************************************************/ +void scp_set_configuration(void) +{ + /* + * SCP Clock Divisor Modulus register + */ + scp_writeb(SCPD_CDM, 0x18); + + /* + * SCP Mode register => Serial port Enabled + loopback (for test) + */ + /* + * scp_writeb(SCPD_SPMODE, SCPD_EN | SCPD_LOOP | SCPD_SCP); + */ + + /* + * SCP Mode register => Serial port Enabled + Serial Clock Phase + */ + scp_writeb(SCPD_SPMODE, SCPD_EN | SCPD_SCP); +} + + +/* + ************************************************************************** + + * scp_rx_data + + * + + * Read the Receive Data Register. Read Only register. + + * + + * Return unsigned char read + + * **********************************************************************/ +unsigned char scp_rx_data(void) +{ + unsigned char buffer = 0x00; + + /* + * In case of loopback the data are inverted + */ + /* + * buffer = ~scp_readb(SCPD_RXDATA); + */ + /* + * start the xfer + */ + //scp_writeb(SCPD_CR, SCPD_STR); /* fscz */ + buffer = (unsigned char) scp_readb(SCPD_RXDATA); +#ifdef SCP_DEBUG + if (scp_verbose) + printk("scp_rx_data: receive data: %#X \n", buffer); +#endif + + return buffer; +} + + +/* + ************************************************************************** + * scp_tx_data + * + * Write data from command on the Transmit Data Register. + * + * Return nothing. + * ************************************************************************/ +void scp_tx_data(unsigned char buffer) +{ + /* + * write data to txdata + */ +#ifdef SCP_DEBUG + if (scp_verbose) + printk("scp_tx_data: Transmit data: %#X \n", buffer); +#endif + scp_writeb(SCPD_TXDATA, buffer); + + /* + * start the xfer + */ + scp_writeb(SCPD_CR, SCPD_STR); +} + +/* + ************************************************************************** + * scp_cs + * + * Enable/Disable Chip Select in the EPLD register (Selection_2_reg). + * + * Return nothing. + * ************************************************************************/ +void scp_cs(int chipid, int high) +{ + ulong val; +#if 1 + val = in_be32((gpio_base+(GPIO0_OR-GPIO_BASE))); + if (!high) { + out_be32((gpio_base+(GPIO0_OR-GPIO_BASE)), val && CS_LOW); + } + else { + out_be32((gpio_base+(GPIO0_OR-GPIO_BASE)), val | CS_HIGH ); + } +#endif +/* CPU 32bit not 64bit + u64 val; + + val = in_be64((gpio_base+(GPIO0_OR-GPIO_BASE))); + if (!high) { + out_be64((gpio_base+(GPIO0_OR-GPIO_BASE)), val && CS_LOW); + } + else { + out_be64((gpio_base+(GPIO0_OR-GPIO_BASE)), val | CS_HIGH ); + } +*/ + +} + +/* + ************************************************************************** + * scp_write_proc: proc interface + * + * This function is called when the user writes in the interface /proc/driver/scp_dev + * + * Commands: works on chip 0 + * CSENA: Enable Chip Select in the EPLD + * CSDIS: Disable Chip Select in the EPLD + * RXDATA: Read Receive Data register + * + * WREN: Set Write Enable Latch 0x06 + * WRDI: Write Disable 0x04 + * RDSR: Read Status 0x05 + * WRSR: Write Status 0x01 + * WRITE: Write Memory Data 0x02 + * READ: Read Memory Data 0x03 + * + * Return the number of data + * + * ************************************************************************/ + +static int +scp_write_proc(struct file *file, const char *buffer, + unsigned long count, void *data) +{ + char str[10]; + int val; +#ifdef SCP_DEBUG + scp_verbose = 1; +#endif + + memset(str, 0, sizeof(str)); + if (copy_from_user(str, buffer, min_t(unsigned long, count, sizeof(str)))) + return -EFAULT; + + /* + * Enable Chip Select in the EPLD + */ + if (!strncmp(str, "CSENA", 5)) + scp_cs(0, 0); + + /* + * Disable Chip Select in the EPLD + */ + else if (!strncmp(str, "CSDIS", 5)) + scp_cs(0, 1); + + /* + * Read RX DATA + */ + else if (!strncmp(str, "RXDATA", 6)) { + scp_rx_data(); + } + /* + * Set Write Enable Latch: 0x06 + */ + else if (!strncmp(str, "WREN", 4)) + scp_tx_data(0x06); + + /* + * Write Disable: 0x04 + */ + else if (!strncmp(str, "WRDI", 4)) + scp_tx_data(0x04); + + /* + * Read Status Command: 0x05 + */ + else if (!strncmp(str, "RDSR", 4)) + scp_tx_data(0x05); + /* + * Write Status Command: 0x01 + */ + else if (!strncmp(str, "WRSR", 4)) + scp_tx_data(0x01); + + /* + * Write Memory Data: 0x02 + */ + else if (!strncmp(str, "WRITE", 5)) + scp_tx_data(0x02); + + /* + * Read Memory Data: 0x03 + */ + else if (!strncmp(str, "READ", 4)) + scp_tx_data(0x03); + + else { + val = simple_strtol(str, NULL, 16); + scp_tx_data((unsigned char) val); + } + return count; +} + +/* + ************************************************************************** + * scp_int_handler: Handle general SCP interrupts. + * + * + * ************************************************************************/ +static irqreturn_t scp_int_handler(int irq, void *dev_id) +{ + unsigned char status_register; + + status_register = scp_readb(SCPD_SR); + //wake_up(&scp_wait); + // Serial Data receive is complete and RxD is available + if (status_register & SCPD_RBR) { +#ifdef SCP_DEBUG + if (scp_verbose) + printk("Int-> "); +#endif + scp_rx_data(); + } + + return IRQ_HANDLED; +} + +int scp_io(int chipid, struct spi_dev_desc *desc, + const unsigned char **inbufs, unsigned int *incounts, + unsigned char **outbufs, unsigned int *outcounts, int cansleep) +{ + unsigned int incount, outcount, tries; + const unsigned char *inp; + unsigned char *outp; + int extra_clock=0; + + /* CS 'L' */ + scp_cs(chipid,0); + udelay(desc->tcss); +#ifdef SCP_DEBUG + scp_verbose = 0; +#endif + /* do scatter IO */ + inp = inbufs ? *inbufs : NULL; + extra_clock = (*inp == ATMEL_RDSR)? 1:0; + incount = *incounts; + while (incount && inp) { + tries = 0; + while (scp_readb(SCPD_SR) & SCPD_SR_BUSY) { + udelay(desc->tcss); + if (tries++ > 20) { + scp_cs(chipid,1); + return -1; + } + } + scp_tx_data(*inp++); + incount --; + if (!incount && *(++inbufs)) { + incount = *(++incounts); + inp = *(inbufs); + } + } +#ifdef SCP_DEBUG + scp_verbose = 0; +#endif + if (extra_clock) { + scp_tx_data(0); + scp_tx_data(0); + } + outp = outbufs ? *outbufs : NULL; + outcount = *outcounts; + while (outcount && outp) { + scp_tx_data(0); /* dummy, for clock */ + //wait_event(scp_wait, scp_readb(SCPD_SR) & SCPD_SR_RBR); + tries = 0; + while (!(scp_readb(SCPD_SR) & SCPD_SR_RBR)) { + udelay(desc->tcss); + if (tries++ > 5) { + return -1; + } + } + *outp++ = scp_rx_data(); + outcount --; + } + udelay(desc->tcss); + /* CS 'H' */ + scp_cs(chipid,1); + + return 0; +} + +static int __devinit scp_request_irq(struct of_device *ofdev, + struct platform_device *dev) +{ + struct device_node *np = ofdev->node; + int irq; + + irq = irq_of_parse_and_map(np, 0); + if (irq == NO_IRQ) { + dev_err(&ofdev->dev, "irq_of_parse_and_map failed\n"); + return NO_IRQ; + } + + /* request interrupt */ + if (request_irq(irq, scp_int_handler , 0, SCP_NAME, dev)) { + dev_err(&ofdev->dev, "request_irq %d failed\n", irq); + return NO_IRQ; + } + + return irq; +} + +/* + ************************************************************************** + * scpdev_cleanup: Clean the module + * + * + * Return nothing. + * ************************************************************************/ +static int __devexit scp_remove(struct of_device *ofdev) +{ + if (scpdev_initialized > 0) { + free_irq(SCP_IRQ, NULL); + iounmap(virtual_scp_add); + virtual_scp_add = NULL; + iounmap(gpio_base); + gpio_base = NULL; + release_mem_region(SCP_PHYS_ADD, 24); + remove_proc_entry("AMCC-SCP", NULL); + unregister_chrdev(major, "AMCC-SCP"); + scpdev_initialized--; + } + return 0; +} + +static int __init scp_probe(struct of_device *ofdev, + const struct of_device_id *match) +{ + SPI_DEV *spi_dev[NUM_SPI_SLAVES]; + struct proc_dir_entry *pentry; + struct platform_device *scp_dev; + int retval = 0, i; + ulong val; + if (scpdev_initialized) { + return -1; + } + + scp_dev = kzalloc(sizeof(*scp_dev), GFP_KERNEL); + if (!scp_dev) { + dev_err(&ofdev->dev, "failed to allocate device data\n"); + return -ENOMEM; + } + dev_set_drvdata(&ofdev->dev, scp_dev); + + if (request_mem_region(SCP_PHYS_ADD, 24, "AMCC-SCP") == NULL) { + retval = -EBUSY; + goto err0; + } + if ((virtual_scp_add = ioremap(SCP_PHYS_ADD, 24)) == NULL) { + retval = -ENOMEM; + goto err1; + } + //printk("virtual_scp_add=0x%p\n", virtual_scp_add); + if ((gpio_base = ioremap(GPIO_BASE, 0x100)) == NULL) { + retval = -ENOMEM; + goto err2; + } +#if 1 + /* select SCP */ + val = SDR_READ(DCRN_SDR_PFC1); + SDR_WRITE(DCRN_SDR_PFC1, val & MASK_SEL_SCP); + + /* enable GPIO output for pin 9, 10, 11 and 14*/ + val = SDR_READ(DCRN_SDR_PFC0); + SDR_WRITE(DCRN_SDR_PFC0, (val & 0xFF8FFFFF) | 0x00700000); + + val = SDR_READ(DCRN_SDR_PFC0); + SDR_WRITE(DCRN_SDR_PFC0, (val & 0xFFFDFFFF) | 0x00020000); + + /* configure GPIO 9 and 11 as SPIClkOut and SPIDO */ + val = in_be32((gpio_base+(GPIO0_OSRL-GPIO_BASE))); + out_be32((gpio_base+(GPIO0_OSRL-GPIO_BASE)), (val & 0xFFFFCCFF) | 0x00001100); + + val = in_be32((gpio_base+(GPIO0_TSRL-GPIO_BASE))); + out_be32((gpio_base+(GPIO0_TSRL-GPIO_BASE)), (val & 0xFFFFCCFF) | 0x00001100); + + val = in_be32((gpio_base+(GPIO0_ISR1L-GPIO_BASE))); + out_be32((gpio_base+(GPIO0_ISR1L-GPIO_BASE)), (val & 0xFFFFF3FF) | 0x00000400); + + /* configure GPIO14 as chip select */ + val = in_be32((gpio_base+(GPIO0_TCR-GPIO_BASE))); + out_be32((gpio_base+(GPIO0_TCR-GPIO_BASE)), (val & 0xFFFDFFFF) | 0x00020000); + + val = in_be32((gpio_base+(GPIO0_TSRL-GPIO_BASE))); + out_be32((gpio_base+(GPIO0_TSRL-GPIO_BASE)), (val & 0xFFFFFFF3) | 0x00000000); + + val = in_be32((gpio_base+(GPIO0_OSRL-GPIO_BASE))); + out_be32((gpio_base+(GPIO0_OSRL-GPIO_BASE)), (val & 0xFFFFFFF3) | 0x00000000); + + val = in_be32((gpio_base+(GPIO0_ODR-GPIO_BASE))); + out_be32((gpio_base+(GPIO0_ODR-GPIO_BASE)), (val & 0xFFFDFFFF) | 0x00000000); + + val = in_be32((gpio_base+(GPIO0_ISR1L-GPIO_BASE))); + out_be32((gpio_base+(GPIO0_ISR1L-GPIO_BASE)), val & GPIO_MASK); + + val = in_be32((gpio_base+(GPIO0_ISR2L-GPIO_BASE))); + out_be32((gpio_base+(GPIO0_ISR2L-GPIO_BASE)), val & GPIO_MASK); + + val = in_be32((gpio_base+(GPIO0_ISR3L-GPIO_BASE))); + out_be32((gpio_base+(GPIO0_ISR3L-GPIO_BASE)), val & GPIO_MASK); + + /* CS high, b14 <= '1' */ + val = in_be32((gpio_base+(GPIO0_OR-GPIO_BASE))); + out_be32((gpio_base+(GPIO0_OR-GPIO_BASE)), val | CS_HIGH ); +#endif + + if ((retval = register_chrdev(major, "AMCC-SCP", &scp_device_fops)) < 0) { + retval = -ENODEV; + goto err3; + } + /* Init the SCP Core */ + scp_set_configuration(); + /* Create proc entry */ + if ((pentry = create_proc_entry(SCP_PROC_NAME, 0200, NULL)) == NULL) { + retval = -ENODEV; + goto err4; + } + pentry->write_proc = scp_write_proc; + /* SCP interrupt registration to Linux. */ + printk(KERN_INFO "SCP: Requesting SCP irq %d ...\n", SCP_IRQ); + /* Request SCP interrupt */ + if ((scp_request_irq(ofdev, scp_dev)) == NO_IRQ) + { + printk(KERN_INFO "SCP: Requesting SCP irq %d ...fail\n", SCP_IRQ); + retval = -EINVAL; + goto err5; + } + + for (i = 0; i < NUM_SPI_SLAVES; i++) { + spi_dev[i] = NULL; + /* Allocate SPI Flash device */ + if ( (spi_dev[i] = (SPI_DEV *)kmalloc(sizeof(SPI_DEV), GFP_KERNEL)) + == NULL) { + retval = -ENOMEM; + goto err6; + } + spi_dev[i]->slaveid = i; + if ((retval = beech_scp_init(spi_dev[i])) != 0) { + printk(KERN_ERR + "beech_scp_init(): %s beech_scp_init err 0x%08x\n", + DEVICE_NAME, retval); + goto err7; + } + } + scpdev_initialized++; + + return 0; +err7: +err6: + for (i = 0; i < NUM_SPI_SLAVES; i++) { + if (spi_dev[i]) { + kfree(spi_dev[i]); + } + } + free_irq(SCP_IRQ, NULL); +err5: + remove_proc_entry("AMCC-SCP", NULL); + unregister_chrdev(major, "AMCC-SCP"); +err4: +err3: + iounmap(gpio_base); + gpio_base = NULL; +err2: + iounmap(virtual_scp_add); + virtual_scp_add = NULL; + +err1: + release_mem_region(SCP_PHYS_ADD, 24); +err0: + return retval; +} + +static const struct of_device_id amcc_scp_match[] = { + { .compatible = "amcc,scp-405ex", }, + {} +}; + +static struct of_platform_driver amcc_scp_driver = { + .name = "AMCC-SCP", + .match_table = amcc_scp_match, + .probe = scp_probe, + .remove = __devexit_p(scp_remove), +}; + +static int __init scp_init(void) +{ + return of_register_platform_driver(&amcc_scp_driver); +} + +static void __exit scp_exit(void) +{ + of_unregister_platform_driver(&amcc_scp_driver); +} + +module_init(scp_init); +module_exit(scp_exit); + +MODULE_AUTHOR("christophe Lombard <christophe_lombard@fr.ibm.com>"); +MODULE_DESCRIPTION("SCP /dev entries driver"); +MODULE_LICENSE("GPL"); |