2008-06-09 18:00:32

by Stefan Hellermann

[permalink] [raw]
Subject: GPIO-Ports on VIA EPIA-SN

Hello,

I have a VIA Epia-SN embedded-board with 8 GPIO-connectors. I got the
attached documentation from VIA after I asked how to use the
GPIO-connectors with Linux.
The GPIO is connected to the VT8251 southbridge, here is lspci-output:

# lspci -vv -s 00:11.0
00:11.0 ISA bridge: VIA Technologies, Inc. VT8251 PCI to ISA Bridge
Subsystem: VIA Technologies, Inc. VT8251 PCI to ISA Bridge
Control: I/O+ Mem+ BusMaster- SpecCycle- MemWINV- VGASnoop- ParErr-
Stepping- SERR- FastB2B- DisINTx-
Status: Cap+ 66MHz- UDF- FastB2B- ParErr- DEVSEL=medium >TAbort-
<TAbort- <MAbort- >SERR- <PERR- INTx-
Capabilities: [c0] Power Management version 2
Flags: PMEClk- DSI- D1- D2- AuxCurrent=0mA
PME(D0-,D1-,D2-,D3hot-,D3cold-)
Status: D0 PME-Enable- DSel=0 DScale=0 PME-


Can anybody help me writing a small kernel-driver for this? I heard of
the GPIO-framework, but I have no clue where to start with. I simply
want to switch these ports to 0 or 1 :).

I don't know under what license this documentation is, I simply got
these 6 pages with sample source code.

Kind Regards
Stefan Hellermann


Attachments:
SN_Digital_IO_Operating Guide_021908 .pdf (59.78 kB)

2008-06-09 19:29:44

by Bart Van Assche

[permalink] [raw]
Subject: Re: GPIO-Ports on VIA EPIA-SN

On Mon, Jun 9, 2008 at 8:00 PM, Stefan Hellermann <[email protected]> wrote:
> I have a VIA Epia-SN embedded-board with 8 GPIO-connectors. I got the
> attached documentation from VIA after I asked how to use the
> GPIO-connectors with Linux.

Are you already familiar with the LDD book
(http://lwn.net/Kernel/LDD3/ +
http://lwn.net/Articles/2.6-kernel-api/) ? That book is a good
starting point, together with the file Documentation/gpio.txt in the
Linux kernel tree.

Bart.

2008-06-09 19:57:29

by David Brownell

[permalink] [raw]
Subject: Re: GPIO-Ports on VIA EPIA-SN

On Monday 09 June 2008, Bart Van Assche wrote:
> On Mon, Jun 9, 2008 at 8:00 PM, Stefan Hellermann <[email protected]> wrote:
> > I have a VIA Epia-SN embedded-board with 8 GPIO-connectors. I got the
> > attached documentation from VIA after I asked how to use the
> > GPIO-connectors with Linux.
>
> Are you already familiar with the LDD book
> (http://lwn.net/Kernel/LDD3/ +
> http://lwn.net/Articles/2.6-kernel-api/) ? That book is a good
> starting point, together with the file Documentation/gpio.txt in the
> Linux kernel tree.

So in short: you'll want to add a Kconfig option for your board,
and when it's selected you'll want to link in some EPIA-specific
int logic. That logic will register a gpio_chip, and then you'll
be able to use the standard GPIO calls (and whatever uses them).

You will also want something like the appended patch ... but do
the "select" commands when your board is selected.

Of course an entirely roll-your-own approach could work too, but
I won't recommend that here.

- Dave


===== CUT HERE
DEBUG ONLY -- make X86_PC use gpiolib.

It's not clear to me how the various x86-ish platforms should
be made to work here, since there seems to be no convention
that each platform type has its own <asm/arch/...> subdir.

---
arch/x86/Kconfig | 2 ++
include/asm-x86/gpio.h | 20 +++++++++++++++++++-
2 files changed, 21 insertions(+), 1 deletion(-)

--- g26.orig/arch/x86/Kconfig 2008-06-03 19:43:53.000000000 -0700
+++ g26/arch/x86/Kconfig 2008-06-03 20:03:25.000000000 -0700
@@ -236,6 +236,8 @@ choice

config X86_PC
bool "PC-compatible"
+ select GENERIC_GPIO
+ select HAVE_GPIO_LIB
help
Choose this option if your computer is a standard PC or compatible.

--- g26.orig/include/asm-x86/gpio.h 2008-06-03 19:43:53.000000000 -0700
+++ g26/include/asm-x86/gpio.h 2008-06-03 20:03:25.000000000 -0700
@@ -1,6 +1,24 @@
#ifndef _ASM_I386_GPIO_H
#define _ASM_I386_GPIO_H

-#include <gpio.h>
+// #include <gpio.h>
+
+#include <linux/errno.h>
+#include <asm-generic/gpio.h> /* cansleep wrappers */
+
+#define gpio_get_value __gpio_get_value
+#define gpio_set_value __gpio_set_value
+#define gpio_cansleep __gpio_cansleep
+
+static inline int gpio_to_irq(unsigned gpio)
+{
+ return -ENOSYS;
+}
+
+static inline int irq_to_gpio(unsigned irq)
+{
+ return -EINVAL;
+}
+

#endif /* _ASM_I386_GPIO_H */

2008-06-10 11:44:24

by Alan

[permalink] [raw]
Subject: Re: GPIO-Ports on VIA EPIA-SN

> Can anybody help me writing a small kernel-driver for this? I heard of
> the GPIO-framework, but I have no clue where to start with. I simply
> want to switch these ports to 0 or 1 :).
>
> I don't know under what license this documentation is, I simply got
> these 6 pages with sample source code.

This is a 5 minute hack on the Nat Semi Geode GPIO driver so you might
need to debug it a bit as its untested.

epiapd_gpio: driver for EPIA-PD

From: Alan Cox <[email protected]>

This is a simple driver for the EPIA-PD GPIO lines
---

drivers/char/Kconfig | 6 +
drivers/char/Makefile | 1
drivers/char/epiapd_gpio.c | 182 ++++++++++++++++++++++++++++++++++++++++++++
3 files changed, 189 insertions(+), 0 deletions(-)
create mode 100644 drivers/char/epiapd_gpio.c


diff --git a/drivers/char/Kconfig b/drivers/char/Kconfig
index 595a925..c308e15 100644
--- a/drivers/char/Kconfig
+++ b/drivers/char/Kconfig
@@ -932,6 +932,12 @@ config GPIO_TB0219
depends on TANBAC_TB022X
select GPIO_VR41XX

+config GPIO_EPIAPD
+ tristate "EPIA-PD VT8251 GPIO support"
+ ---help---
+ This driver provides access to the GPI/GPO pins on the VT8251
+ EPIA-PD board.
+
source "drivers/char/pcmcia/Kconfig"

config MWAVE
diff --git a/drivers/char/Makefile b/drivers/char/Makefile
index 4c1c584..237ebfc 100644
--- a/drivers/char/Makefile
+++ b/drivers/char/Makefile
@@ -96,6 +96,7 @@ obj-$(CONFIG_PC8736x_GPIO) += pc8736x_gpio.o
obj-$(CONFIG_NSC_GPIO) += nsc_gpio.o
obj-$(CONFIG_CS5535_GPIO) += cs5535_gpio.o
obj-$(CONFIG_GPIO_VR41XX) += vr41xx_giu.o
+obj-$(CONFIG_GPIO_EPIAPD) += epiapd_gpio.o
obj-$(CONFIG_GPIO_TB0219) += tb0219.o
obj-$(CONFIG_TELCLOCK) += tlclk.o

diff --git a/drivers/char/epiapd_gpio.c b/drivers/char/epiapd_gpio.c
new file mode 100644
index 0000000..eedb4ae
--- /dev/null
+++ b/drivers/char/epiapd_gpio.c
@@ -0,0 +1,182 @@
+/* linux/drivers/char/epiapd_gpio.c
+
+ EPIA-PD GPIO driver. Allows a user space process to play with the GPIO pins.
+
+ based on the Nat Semi GPIO driver
+ Copyright (c) 2001,2002 Christer Weinigel <[email protected]>
+*/
+
+#include <linux/device.h>
+#include <linux/fs.h>
+#include <linux/module.h>
+#include <linux/errno.h>
+#include <linux/kernel.h>
+#include <linux/init.h>
+#include <linux/uaccess.h>
+#include <linux/io.h>
+#include <linux/pci.h>
+#include <linux/types.h>
+#include <linux/cdev.h>
+
+#define DRVNAME "epiapd_gpio"
+
+MODULE_AUTHOR("Alan Cox <[email protected]>");
+MODULE_DESCRIPTION("EPIA PD VIA VT8251 GPIO Pin Driver");
+MODULE_LICENSE("GPL");
+
+static int major = 0; /* default to dynamic major */
+module_param(major, int, 0);
+MODULE_PARM_DESC(major, "Major device number");
+
+#define MAX_PINS 8
+
+static unsigned long pmio;
+
+static void epiapd_gpio_set(int m, int v)
+{
+ u8 r = inb(pmio + 0x4C);
+ r &= ~ (1 << m);
+ r |= (v << m);
+ outb(r, pmio + 0x4C);
+}
+
+ssize_t epiapd_gpio_write(struct file *file, const char __user *data,
+ size_t len, loff_t *ppos)
+{
+ unsigned m = iminor(file->f_path.dentry->d_inode);
+ size_t i;
+ int err = 0;
+
+ for (i = 0; i < len; ++i) {
+ char c;
+ if (get_user(c, data + i)) {
+ err = -EFAULT;
+ break;
+ }
+ switch (c) {
+ case '0':
+ epiapd_gpio_set(m, 0);
+ break;
+ case '1':
+ epiapd_gpio_set(m, 1);
+ break;
+ case '\n':
+ /* end of settings string, do nothing */
+ break;
+ default:
+ err = -EINVAL;
+ break;
+ }
+ }
+ if (err) {
+ if(i == 0)
+ return err;
+ else
+ return i;
+ }
+ return len;
+}
+
+ssize_t epiapd_gpio_read(struct file *file, char __user * buf,
+ size_t len, loff_t * ppos)
+{
+ unsigned m = iminor(file->f_path.dentry->d_inode);
+ u8 r;
+ u8 c = '0';
+
+ r = inb(pmio + 0x47) & 7;
+ r |= (inb(pmio + 0x49) & 1) << 3;
+
+ if (r & (1 << m))
+ c = '1';
+
+ if (put_user(c, buf))
+ return -EFAULT;
+ return 1;
+}
+
+static int epiapd_gpio_open(struct inode *inode, struct file *file)
+{
+ unsigned m = iminor(inode);
+ if (m >= MAX_PINS)
+ return -EINVAL;
+ return nonseekable_open(inode, file);
+}
+
+static int epiapd_gpio_release(struct inode *inode, struct file *file)
+{
+ return 0;
+}
+
+static const struct file_operations epiapd_gpio_fileops = {
+ .owner = THIS_MODULE,
+ .write = epiapd_gpio_write,
+ .read = epiapd_gpio_read,
+ .open = epiapd_gpio_open,
+ .release = epiapd_gpio_release,
+};
+
+static struct cdev epiapd_gpio_cdev; /* use 1 cdev for all pins */
+
+static int __init epiapd_gpio_configure(void)
+{
+ struct pci_dev *pdev = pci_get_device(PCI_VENDOR_ID_VIA,
+ PCI_DEVICE_ID_VIA_8251, NULL);
+ u32 v;
+ u8 r;
+ if (pdev == NULL)
+ return -ENODEV;
+ pci_read_config_dword(pdev, 0x8B, &v);
+ v &= 0xFFFE;
+ if (v == 0)
+ return -ENODEV;
+ pmio = v;
+
+ /* Program the GPIO direction bits */
+ pci_read_config_byte(pdev, 0xE4, &r);
+ r &= 0x08;
+ pci_write_config_byte(pdev, 0xE4, r);
+ pci_read_config_byte(pdev, 0x95, &r);
+ r |= 0x02;
+ pci_write_config_byte(pdev, 0x95, r);
+ pci_read_config_byte(pdev, 0xE4, &r);
+ r |= 0x06;
+ pci_write_config_byte(pdev, 0xE4, r);
+
+ pci_dev_put(pdev);
+ return 0;
+}
+
+static int __init epiapd_gpio_init(void)
+{
+ int rc;
+ dev_t devid;
+
+ if (epiapd_gpio_configure() < 0) {
+ printk(KERN_ERR DRVNAME ": no VT8251 gpio present\n");
+ return -ENODEV;
+ }
+
+ if (major) {
+ devid = MKDEV(major, 0);
+ rc = register_chrdev_region(devid, MAX_PINS, "epiapd_gpio");
+ } else {
+ rc = alloc_chrdev_region(&devid, 0, MAX_PINS, "epiapd_gpio");
+ major = MAJOR(devid);
+ }
+ if (rc < 0)
+ return rc;
+ cdev_init(&epiapd_gpio_cdev, &epiapd_gpio_fileops);
+ cdev_add(&epiapd_gpio_cdev, devid, MAX_PINS);
+ return 0; /* succeed */
+}
+
+static void __exit epiapd_gpio_cleanup(void)
+{
+ cdev_del(&epiapd_gpio_cdev);
+ /* cdev_put(&epiapd_gpio_cdev); */
+ unregister_chrdev_region(MKDEV(major, 0), MAX_PINS);
+}
+
+module_init(epiapd_gpio_init);
+module_exit(epiapd_gpio_cleanup);

2008-06-10 22:52:04

by Stefan Hellermann

[permalink] [raw]
Subject: [patch 1/1] Add driver for GPIO on Via Epia-SN - vt8251

This is a first version which probably doesn't work. I wrote a small
userspace tool which uses the same ioports and it works. I can't test
this driver yet, because I don't know how to set or check bits with the
GPIO-framework.

Signed-off-by: Stefan Hellermann <[email protected]>

---

> > Can anybody help me writing a small kernel-driver for this? I heard of
> > the GPIO-framework, but I have no clue where to start with. I simply
> > want to switch these ports to 0 or 1 :).
> >
> This is a 5 minute hack on the Nat Semi Geode GPIO driver so you might
> need to debug it a bit as its untested.
>

Thanks a lot for your work! It's so much easier to start with something
existing ...

I changed some lines and rewrote epiapd_gpio_set() because the output
bits are at position 1,4,6,7, not 0-3.

This is my first patch so please check everything! I already corrected
most errors spotted by scripts/checkpatch.pl.

My only problem: How to use this? Are there any sysfs-files I can use to
send bits to the device? Probably I have to enable the GPIO-framework
for the x86-architecture first. I tried some changes from David
Brownell to use gpiolib with x86 but these crashed my machine with
irq-failures (or maybe I simply sent the wrong bytes to the wrong
registers and then got crashes)

diff --git a/drivers/char/Kconfig b/drivers/char/Kconfig
index d307bf2..fc4fbec 100644
--- a/drivers/char/Kconfig
+++ b/drivers/char/Kconfig
@@ -932,6 +932,12 @@ config GPIO_TB0219
depends on TANBAC_TB022X
select GPIO_VR41XX

+config GPIO_EPIAPD
+ tristate "EPIA-PD VT8251 GPIO support"
+ ---help---
+ This driver provides access to the GPI/GPO pins on the VT8251
+ EPIA-PD board.
+
source "drivers/char/pcmcia/Kconfig"

config MWAVE
diff --git a/drivers/char/Makefile b/drivers/char/Makefile
index 4c1c584..b1e3234 100644
--- a/drivers/char/Makefile
+++ b/drivers/char/Makefile
@@ -96,6 +96,7 @@ obj-$(CONFIG_PC8736x_GPIO) += pc8736x_gpio.o
obj-$(CONFIG_NSC_GPIO) += nsc_gpio.o
obj-$(CONFIG_CS5535_GPIO) += cs5535_gpio.o
obj-$(CONFIG_GPIO_VR41XX) += vr41xx_giu.o
+obj-$(CONFIG_GPIO_EPIAPD) += epiapd_gpio.o
obj-$(CONFIG_GPIO_TB0219) += tb0219.o
obj-$(CONFIG_TELCLOCK) += tlclk.o

diff --git a/drivers/char/epiapd_gpio.c b/drivers/char/epiapd_gpio.c
new file mode 100644
index 0000000..ef4d08a
--- /dev/null
+++ b/drivers/char/epiapd_gpio.c
@@ -0,0 +1,202 @@
+/* linux/drivers/char/epiapd_gpio.c
+ *
+ * This driver provides access to the GPI/GPO pins on the VT8251
+ *
+ * based on the Nat Semi GPIO driver
+ * Copyright (c) 2001,2002 Christer Weinigel <[email protected]>
+*/
+
+#include <linux/device.h>
+#include <linux/fs.h>
+#include <linux/module.h>
+#include <linux/errno.h>
+#include <linux/kernel.h>
+#include <linux/init.h>
+#include <linux/uaccess.h>
+#include <linux/io.h>
+#include <linux/pci.h>
+#include <linux/types.h>
+#include <linux/cdev.h>
+
+#define DRVNAME "epiapd_gpio"
+
+MODULE_AUTHOR("Alan Cox <[email protected]>");
+MODULE_DESCRIPTION("EPIA-PD VIA VT8251 GPIO Pin Driver");
+MODULE_LICENSE("GPL");
+
+static int major = 0; /* default to dynamic major */
+module_param(major, int, 0);
+MODULE_PARM_DESC(major, "Major device number");
+
+#define MAX_PINS 4 /* 4 input and 4 output */
+
+static u32 pmio;
+
+static void
+epiapd_gpio_set(int m, int v)
+{
+ u8 r = inb(pmio + 0x4C);
+
+ BUG_ON(m > 3);
+ BUG_ON(v > 1 || v < 0);
+
+ switch (m) {
+ case 0:
+ m = 1;
+ break;
+ case 1:
+ m = 4;
+ break;
+ case 2:
+ m = 6;
+ break;
+ case 3:
+ m = 7;
+ break;
+ }
+ r &= ~(1 << m);
+ r |= (v << m);
+ outb(r, pmio + 0x4C);
+}
+
+ssize_t epiapd_gpio_write(struct file *file, const char __user *data,
+ size_t len, loff_t *ppos)
+{
+ unsigned m = iminor(file->f_path.dentry->d_inode);
+ size_t i;
+ int err = 0;
+
+ for (i = 0; i < len; ++i) {
+ char c;
+ if (get_user(c, data + i)) {
+ err = -EFAULT;
+ break;
+ }
+ switch (c) {
+ case '0':
+ epiapd_gpio_set(m, 0);
+ break;
+ case '1':
+ epiapd_gpio_set(m, 1);
+ break;
+ case '\n':
+ /* end of settings string, do nothing */
+ break;
+ default:
+ err = -EINVAL;
+ break;
+ }
+ }
+ if (err && (i == 0))
+ return err;
+ return i;
+}
+
+ssize_t epiapd_gpio_read(struct file *file, char __user *buf,
+ size_t len, loff_t *ppos)
+{
+ unsigned m = iminor(file->f_path.dentry->d_inode);
+ u8 r;
+ u8 c = '0';
+
+ r = inb(pmio + 0x47) & 7;
+ r |= (inb(pmio + 0x49) & 1) << 3;
+
+ if (r & (1 << m))
+ c = '1';
+
+ if (put_user(c, buf))
+ return -EFAULT;
+ return 1;
+}
+
+static int epiapd_gpio_open(struct inode *inode, struct file *file)
+{
+ unsigned m = iminor(inode);
+ if (m >= MAX_PINS)
+ return -EINVAL;
+ return nonseekable_open(inode, file);
+}
+
+static int epiapd_gpio_release(struct inode *inode, struct file *file)
+{
+ return 0;
+}
+
+static const struct file_operations epiapd_gpio_fileops = {
+ .owner = THIS_MODULE,
+ .write = epiapd_gpio_write,
+ .read = epiapd_gpio_read,
+ .open = epiapd_gpio_open,
+ .release = epiapd_gpio_release,
+};
+
+static struct cdev epiapd_gpio_cdev; /* use 1 cdev for all pins */
+
+static int __init epiapd_gpio_configure(void)
+{
+ struct pci_dev *pdev = pci_get_device(PCI_VENDOR_ID_VIA,
+ PCI_DEVICE_ID_VIA_8251, NULL);
+ u8 r;
+ if (pdev == NULL)
+ return -ENODEV;
+
+ /* get vt8251 pmio base address */
+ pci_read_config_dword(pdev, 0x88, &pmio);
+ pmio &= 0xFFFE;
+ if (pmio == 0)
+ return -ENODEV;
+
+ printk(KERN_DEBUG DRVNAME " pmio baseaddress: 0x%x\n", pmio);
+
+ /* Program the GPIO enable bits */
+ pci_read_config_byte(pdev, 0xE4, &r);
+ r &= ~0x0c; /* bit 3+2 = 0 */
+ r |= 0x02; /* bit 1 = 1 */
+ pci_write_config_byte(pdev, 0xE4, r);
+ pci_read_config_byte(pdev, 0x95, &r);
+ r |= 0x02; /* bit 1 = 1 */
+ pci_write_config_byte(pdev, 0x95, r);
+
+ /* is this the right position for pci_dev_put? */
+ pci_dev_put(pdev);
+
+ return 0;
+}
+
+static int __init epiapd_gpio_init(void)
+{
+ int rc;
+ dev_t devid;
+
+ return epiapd_gpio_configure();
+
+ if (major) {
+ devid = MKDEV(major, 0);
+ rc = register_chrdev_region(devid, MAX_PINS, "epiapd_gpio");
+ } else {
+ rc = alloc_chrdev_region(&devid, 0, MAX_PINS, "epiapd_gpio");
+ major = MAJOR(devid);
+ }
+ if (rc < 0)
+ return rc;
+
+ printk(KERN_DEBUG DRVNAME " major: %d\n", major);
+
+ cdev_init(&epiapd_gpio_cdev, &epiapd_gpio_fileops);
+ cdev_add(&epiapd_gpio_cdev, devid, MAX_PINS);
+
+ return 0;
+}
+
+static void __exit epiapd_gpio_cleanup(void)
+{
+ cdev_del(&epiapd_gpio_cdev);
+
+ /* I don't know why the next is a comment */
+ /* cdev_put(&epiapd_gpio_cdev); */
+ unregister_chrdev_region(MKDEV(major, 0), MAX_PINS);
+}
+
+module_init(epiapd_gpio_init);
+module_exit(epiapd_gpio_cleanup);