lkml.org 
[lkml]   [2010]   [Nov]   [9]   [last100]   RSS Feed
Views: [wrap][no wrap]   [headers]  [forward] 
 
Messages in this thread
Patch in this message
/
From
Subject[PATCHv2] drivers/misc: Altera Cyclone active serial implementation
Date
The active serial protocol can be used to program Altera Cyclone FPGA devices.
This driver uses the kernel gpio interface to implement the active serial
protocol.

Signed-off-by: Alex Gershgorin <agersh@rambler.ru>
Signed-off-by: Baruch Siach <baruch@tkos.co.il>
---
Changes in v2:
* Don't create a new class, use the misc class instead

* Depend on GENERIC_GPIO

* Simplify xmit_byte(), use bitrev8() to flip data bytes

* Add xmit_cmd() to simplify send of simple commands

* Remove unnecessary debug prints

* Check msleep_interruptible() return value

* Remove unneeded ndelay() calls

* Poll the write-in-progress indicator instead of waiting for a fixed (and
too short) period of time after erase.

drivers/misc/Kconfig | 11 +
drivers/misc/Makefile | 1 +
drivers/misc/cyclone_as.c | 343 +++++++++++++++++++++++++++++++++
include/platform_drivers/cyclone_as.h | 27 +++
4 files changed, 382 insertions(+), 0 deletions(-)
create mode 100644 drivers/misc/cyclone_as.c
create mode 100644 include/platform_drivers/cyclone_as.h
diff --git a/drivers/misc/Kconfig b/drivers/misc/Kconfig
index 4d073f1..70534bf 100644
--- a/drivers/misc/Kconfig
+++ b/drivers/misc/Kconfig
@@ -452,6 +452,17 @@ config PCH_PHUB
To compile this driver as a module, choose M here: the module will
be called pch_phub.

+config CYCLONE_AS
+ depends on GENERIC_GPIO
+ tristate "Altera Cyclone Active Serial driver"
+ help
+ Provides support for active serial programming of Altera Cyclone
+ devices. For the active serial protocol details see the Altera
+ "Serial Configuration Devices" document (C51014).
+
+ To compile this driver as a module, choose M here: the
+ module will be called cyclone_as.
+
source "drivers/misc/c2port/Kconfig"
source "drivers/misc/eeprom/Kconfig"
source "drivers/misc/cb710/Kconfig"
diff --git a/drivers/misc/Makefile b/drivers/misc/Makefile
index 98009cc..58c6548 100644
--- a/drivers/misc/Makefile
+++ b/drivers/misc/Makefile
@@ -42,3 +42,4 @@ obj-$(CONFIG_ARM_CHARLCD) += arm-charlcd.o
obj-$(CONFIG_PCH_PHUB) += pch_phub.o
obj-y += ti-st/
obj-$(CONFIG_AB8500_PWM) += ab8500-pwm.o
+obj-$(CONFIG_CYCLONE_AS) += cyclone_as.o
diff --git a/drivers/misc/cyclone_as.c b/drivers/misc/cyclone_as.c
new file mode 100644
index 0000000..cdae2a7
--- /dev/null
+++ b/drivers/misc/cyclone_as.c
@@ -0,0 +1,343 @@
+/*
+ * Copyright 2010 Alex Gershgorin, Orex Computed Radiography
+ *
+ * The code contained herein is licensed under the GNU General Public
+ * License. You may obtain a copy of the GNU General Public License
+ * Version 2 or later at the following locations:
+ *
+ * http://www.opensource.org/licenses/gpl-license.html
+ * http://www.gnu.org/licenses/old-licenses/gpl-2.0.html
+ */
+
+#include <linux/fs.h>
+#include <linux/err.h>
+#include <linux/gpio.h>
+#include <linux/slab.h>
+#include <linux/mutex.h>
+#include <linux/delay.h>
+#include <linux/bitrev.h>
+#include <linux/device.h>
+#include <linux/uaccess.h>
+#include <linux/miscdevice.h>
+#include <linux/platform_device.h>
+
+#include <platform_drivers/cyclone_as.h>
+
+/* Active Serial Instruction Set */
+#define AS_WRITE_ENABLE 0x06
+#define AS_WRITE_DISABLE 0x04
+#define AS_READ_STATUS 0x05
+#define AS_WRITE_STATUS 0x01
+#define AS_READ_BYTES 0x03
+#define AS_FAST_READ_BYTES 0x0B
+#define AS_PAGE_PROGRAM 0x02
+#define AS_ERASE_SECTOR 0xD8
+#define AS_ERASE_BULK 0xC7
+#define AS_READ_SILICON_ID 0xAB
+#define AS_CHECK_SILICON_ID 0x9F
+
+#define AS_STATUS_WIP (1 << 0)
+#define AS_STATUS_WEL (1 << 1)
+#define AS_STATUS_BP0 (1 << 2)
+#define AS_STATUS_BP1 (1 << 3)
+#define AS_STATUS_BP2 (1 << 4)
+
+#define AS_ERASE_TIMEOUT 250 /* seconds, max for EPCS128 */
+
+#define AS_PAGE_SIZE 256
+
+#define AS_MAX_DEVS 16
+
+#define ASIO_DATA 0
+#define ASIO_ASDI 1
+#define ASIO_NCONFIG 2
+#define ASIO_DCLK 3
+#define ASIO_NCS 4
+#define ASIO_NCE 5
+#define AS_GPIO_NUM 6
+
+#define AS_ALIGNED(x) IS_ALIGNED(x, AS_PAGE_SIZE)
+
+struct cyclone_as_device {
+ unsigned id;
+ struct device *dev;
+ struct miscdevice miscdev;
+ struct mutex open_lock;
+ struct gpio gpios[AS_GPIO_NUM];
+};
+
+static struct {
+ int minor;
+ struct cyclone_as_device *drvdata;
+} cyclone_as_devs[AS_MAX_DEVS];
+
+static struct cyclone_as_device *get_as_dev(int minor)
+{
+ int i;
+
+ for (i = 0; i < ARRAY_SIZE(cyclone_as_devs); i++)
+ if (cyclone_as_devs[i].minor == minor)
+ return cyclone_as_devs[i].drvdata;
+
+ return NULL;
+}
+
+static void as_clk_tick(struct gpio *gpios)
+{
+ gpio_set_value(gpios[ASIO_DCLK].gpio, 0);
+ ndelay(40);
+ gpio_set_value(gpios[ASIO_DCLK].gpio, 1);
+ ndelay(20);
+}
+
+static void xmit_byte(struct gpio *gpios, u8 c)
+{
+ int i;
+
+ for (i = 0; i < 8; i++) {
+ gpio_set_value(gpios[ASIO_ASDI].gpio, (c << i) & 0x80);
+ as_clk_tick(gpios);
+ }
+}
+
+static void xmit_cmd(struct gpio *gpios, u8 cmd)
+{
+ gpio_set_value(gpios[ASIO_NCS].gpio, 0);
+ xmit_byte(gpios, cmd);
+ gpio_set_value(gpios[ASIO_NCS].gpio, 1);
+ ndelay(300);
+}
+
+static u8 recv_byte(struct gpio *gpios)
+{
+ int i;
+ u8 val = 0;
+
+ for (i = 0; i < 8; i++) {
+ as_clk_tick(gpios);
+ val |= (!!gpio_get_value(gpios[ASIO_DATA].gpio) << (7 - i));
+ }
+
+ return val;
+}
+
+static int cyclone_as_open(struct inode *inode, struct file *file)
+{
+ int ret;
+ struct cyclone_as_device *drvdata = get_as_dev(iminor(inode));
+
+ if (drvdata == NULL)
+ return -ENODEV;
+ file->private_data = drvdata;
+
+ ret = mutex_trylock(&drvdata->open_lock);
+ if (ret == 0)
+ return -EBUSY;
+
+ ret = gpio_request_array(drvdata->gpios, ARRAY_SIZE(drvdata->gpios));
+ if (ret < 0) {
+ mutex_unlock(&drvdata->open_lock);
+ return ret;
+ }
+
+ return 0;
+}
+
+static ssize_t cyclone_as_write(struct file *file, const char __user *buf,
+ size_t count, loff_t *ppos)
+{
+ int i, err = 0;
+ u8 *page_buf;
+ ssize_t written = 0;
+ struct cyclone_as_device *drvdata = file->private_data;
+ unsigned page_count;
+
+ if (count == 0)
+ return 0;
+
+ /* writes must be page aligned */
+ if (!AS_ALIGNED(*ppos) || !AS_ALIGNED(count))
+ return -EINVAL;
+ page_count = *ppos / AS_PAGE_SIZE;
+
+ page_buf = kmalloc(AS_PAGE_SIZE, GFP_KERNEL);
+ if (page_buf == NULL)
+ return -ENOMEM;
+
+ if (*ppos == 0) {
+ u8 as_status;
+
+ xmit_cmd(drvdata->gpios, AS_WRITE_ENABLE);
+ xmit_cmd(drvdata->gpios, AS_ERASE_BULK);
+
+ gpio_set_value(drvdata->gpios[ASIO_NCS].gpio, 0);
+ xmit_byte(drvdata->gpios, AS_READ_STATUS);
+ for (i = 0; i < AS_ERASE_TIMEOUT; i++) {
+ as_status = recv_byte(drvdata->gpios);
+ if ((as_status & AS_STATUS_WIP) == 0)
+ break; /* erase done */
+ if (msleep_interruptible(1000) > 0) {
+ err = -EINTR;
+ break;
+ }
+ }
+ gpio_set_value(drvdata->gpios[ASIO_NCS].gpio, 1);
+ if ((as_status & AS_STATUS_WIP) && err == 0)
+ err = -EIO; /* erase timeout */
+ if (err)
+ goto out;
+ ndelay(300);
+ }
+
+ while (count) {
+ dev_dbg(drvdata->dev, "offset = 0x%p\n", buf+written);
+
+ err = copy_from_user(page_buf, buf+written, AS_PAGE_SIZE);
+ if (err < 0) {
+ err = -EFAULT;
+ goto out;
+ }
+
+ *ppos += AS_PAGE_SIZE;
+ count -= AS_PAGE_SIZE;
+ written += AS_PAGE_SIZE;
+
+ xmit_cmd(drvdata->gpios, AS_WRITE_ENABLE);
+
+ gpio_set_value(drvdata->gpios[ASIO_NCS].gpio, 0);
+ /* op code */
+ xmit_byte(drvdata->gpios, AS_PAGE_PROGRAM);
+ /* address */
+ xmit_byte(drvdata->gpios, (page_count >> 8) & 0xff);
+ xmit_byte(drvdata->gpios, page_count & 0xff);
+ xmit_byte(drvdata->gpios, 0);
+ /* page data (LSB first) */
+ for (i = 0; i < AS_PAGE_SIZE; i++)
+ xmit_byte(drvdata->gpios, bitrev8(page_buf[i]));
+ gpio_set_value(drvdata->gpios[ASIO_NCS].gpio, 1);
+ page_count++;
+ mdelay(7);
+ }
+
+out:
+ kfree(page_buf);
+ return err ?: written;
+}
+
+static int cyclone_as_release(struct inode *inode, struct file *file)
+{
+ struct cyclone_as_device *drvdata = file->private_data;
+ int i;
+
+ gpio_set_value(drvdata->gpios[ASIO_NCONFIG].gpio, 1);
+ gpio_set_value(drvdata->gpios[ASIO_NCE].gpio, 0);
+ gpio_set_value(drvdata->gpios[ASIO_DCLK].gpio, 0);
+ ndelay(500);
+
+ for (i = 0; i < ARRAY_SIZE(drvdata->gpios); i++)
+ gpio_direction_input(drvdata->gpios[i].gpio);
+ gpio_free_array(drvdata->gpios, ARRAY_SIZE(drvdata->gpios));
+ mutex_unlock(&drvdata->open_lock);
+
+ return 0;
+}
+
+static const struct file_operations cyclone_as_fops = {
+ .open = cyclone_as_open,
+ .write = cyclone_as_write,
+ .release = cyclone_as_release,
+};
+
+static int __init cyclone_as_probe(struct platform_device *pdev)
+{
+ struct cyclone_as_device *drvdata;
+ struct cyclone_as_platform_data *pdata = pdev->dev.platform_data;
+
+ if (pdata->id >= AS_MAX_DEVS)
+ return -ENODEV;
+
+ if (cyclone_as_devs[pdata->id].drvdata)
+ return -EBUSY;
+
+ drvdata = devm_kzalloc(&pdev->dev, sizeof(*drvdata), GFP_KERNEL);
+ if (!drvdata)
+ return -ENOMEM;
+
+ drvdata->dev = &pdev->dev;
+ platform_set_drvdata(pdev, drvdata);
+
+ drvdata->miscdev.minor = MISC_DYNAMIC_MINOR;
+ drvdata->miscdev.name = kasprintf(GFP_KERNEL, "cyclone_as%d",
+ pdata->id);
+ if (drvdata->miscdev.name == NULL)
+ return -ENOMEM;
+ drvdata->miscdev.fops = &cyclone_as_fops;
+ if (misc_register(&drvdata->miscdev) < 0) {
+ kfree(drvdata->miscdev.name);
+ return -ENODEV;
+ }
+ cyclone_as_devs[pdata->id].minor = drvdata->miscdev.minor;
+ cyclone_as_devs[pdata->id].drvdata = drvdata;
+
+ mutex_init(&drvdata->open_lock);
+
+ drvdata->id = pdata->id;
+
+ drvdata->gpios[ASIO_DATA].gpio = pdata->data;
+ drvdata->gpios[ASIO_DATA].flags = GPIOF_IN;
+ drvdata->gpios[ASIO_DATA].label = "as_data";
+ drvdata->gpios[ASIO_ASDI].gpio = pdata->asdi;
+ drvdata->gpios[ASIO_ASDI].flags = GPIOF_OUT_INIT_LOW;
+ drvdata->gpios[ASIO_ASDI].label = "as_asdi";
+ drvdata->gpios[ASIO_NCONFIG].gpio = pdata->nconfig;
+ drvdata->gpios[ASIO_NCONFIG].flags = GPIOF_OUT_INIT_LOW;
+ drvdata->gpios[ASIO_NCONFIG].label = "as_nconfig";
+ drvdata->gpios[ASIO_DCLK].gpio = pdata->dclk;
+ drvdata->gpios[ASIO_DCLK].flags = GPIOF_OUT_INIT_LOW;
+ drvdata->gpios[ASIO_DCLK].label = "as_dclk";
+ drvdata->gpios[ASIO_NCS].gpio = pdata->ncs;
+ drvdata->gpios[ASIO_NCS].flags = GPIOF_OUT_INIT_HIGH;
+ drvdata->gpios[ASIO_NCS].label = "as_ncs";
+ drvdata->gpios[ASIO_NCE].gpio = pdata->nce;
+ drvdata->gpios[ASIO_NCE].flags = GPIOF_OUT_INIT_HIGH;
+ drvdata->gpios[ASIO_NCE].label = "as_nce";
+
+ dev_info(drvdata->dev, "Altera Cyclone Active Serial driver\n");
+
+ return 0;
+}
+
+static int __devexit cyclone_as_remove(struct platform_device *pdev)
+{
+ struct cyclone_as_device *drvdata = platform_get_drvdata(pdev);
+
+ cyclone_as_devs[drvdata->id].drvdata = NULL;
+ kfree(drvdata->miscdev.name);
+
+ return misc_deregister(&drvdata->miscdev);
+}
+
+static struct platform_driver cyclone_as_driver = {
+ .driver = {
+ .owner = THIS_MODULE,
+ .name = "cyclone_as",
+ },
+ .remove = __devexit_p(cyclone_as_remove),
+};
+
+int __init cyclone_as_init(void)
+{
+ return platform_driver_probe(&cyclone_as_driver, cyclone_as_probe);
+}
+
+void __exit cyclone_as_cleanup(void)
+{
+ platform_driver_unregister(&cyclone_as_driver);
+}
+
+module_init(cyclone_as_init);
+module_exit(cyclone_as_cleanup);
+
+MODULE_AUTHOR("Alex Gershgorin <agersh@rambler.ru>");
+MODULE_DESCRIPTION("Altera Cyclone Active Serial driver");
+MODULE_LICENSE("GPL");
diff --git a/include/platform_drivers/cyclone_as.h b/include/platform_drivers/cyclone_as.h
new file mode 100644
index 0000000..44c8407
--- /dev/null
+++ b/include/platform_drivers/cyclone_as.h
@@ -0,0 +1,27 @@
+/* include/linux/cyclone_as.h
+ *
+ * Copyright 2010 Alex Gershgorin, Orex Computed Radiography
+ *
+ * The code contained herein is licensed under the GNU General Public
+ * License. You may obtain a copy of the GNU General Public License
+ * Version 2 or later at the following locations:
+ *
+ * http://www.opensource.org/licenses/gpl-license.html
+ * http://www.gnu.org/licenses/old-licenses/gpl-2.0.html
+ */
+
+#ifndef __LINUX_CYCLONE_AS_H__
+#define __LINUX_CYCLONE_AS_H__
+
+struct cyclone_as_platform_data {
+ unsigned id; /* instance number */
+
+ unsigned data;
+ unsigned asdi;
+ unsigned nconfig;
+ unsigned dclk;
+ unsigned ncs;
+ unsigned nce;
+};
+
+#endif /* __LINUX_CYCLONE_AS_H__ */
--
1.7.2.3


\
 
 \ /
  Last update: 2010-11-09 11:11    [from the cache]
©2003-2011 Jasper Spaans