[RFC-patch] add GPIO-Sysfs interface to scx200_gpio, pc8736x_gpio

From: Jim Cromie
Date: Tue Jul 18 2006 - 13:44:23 EST



heres a rough, RFC, proto-patch which adds a GPIO-sysfs interface
into scx200_gpio, and pc8736x_gpio.

Im going on vacation, and thought I might solicit an inbox full of bright ideas
to bring me back to (a much sharper) focus upon my return.

heres a bit of testing which raises a few questions:
$> cat remod
#!/bin/bash
loudly() {
echo
echo running: $*
eval $*
}
rmmod pc8736x_gpio
modprobe pc8736x_gpio
cd ~/pinlab
check123
#loudly cat /dev/led
#loudly cat /dev/gpio-17
loudly cat /sys/devices/platform/scx200_gpio.0/bit_0.20_value
loudly cat /sys/devices/platform/pc8736x_gpio.0/bit_2.1_value
loudly scansimple -d /dev/led
loudly scansimple -d /dev/gpio-17
loudly scansimple -d /sys/devices/platform/scx200_gpio.0/bit_0.20_value
loudly scansimple -d /sys/devices/platform/pc8736x_gpio.0/bit_2.1_value


soekris:~/pinlab# remod
[ 5729.928000] lockd: cannot monitor 192.168.42.1
[ 5729.932000] lockd: failed to monitor 192.168.42.1
[ 5729.940000] lockd: cannot monitor 192.168.42.1
[ 5729.944000] lockd: failed to monitor 192.168.42.1
[ 5730.468000] platform pc8736x_gpio.0: NatSemi pc8736x GPIO Driver Initializing
[ 5730.508000] platform pc8736x_gpio.0: GPIO ioport 6600 reserved
[ 5730.512000] platform pc8736x_gpio.0: pc8736x_sysfs_init creating pin-attr nodes
[ 5730.532000] platform pc8736x_gpio.0: creating gpio port-attrs
/dev/gpio-[0..31]: 11111111-10001011-01111001-10111000
/dev/scxio-[0..31]: 11100000-00001110-01000000-00000000

running: cat /sys/devices/platform/scx200_gpio.0/bit_0.20_value
[ 5732.836000] platform scx200_gpio.0: nsc_gpio_sysfs_get(V) = 0
0

running: cat /sys/devices/platform/pc8736x_gpio.0/bit_2.1_value
[ 5732.864000] platform pc8736x_gpio.0: nsc_gpio_sysfs_get(V) = 1
1

running: scansimple -d /dev/led
opened /dev/led, for 1 loops, 1000000 samples
read 1000000 samples in 4.5966 sec, rate: 217552.5750 samples/sec

running: scansimple -d /dev/gpio-17
opened /dev/gpio-17, for 1 loops, 1000000 samples
read 1000000 samples in 6.5261 sec, rate: 153230.0295 samples/sec

running: scansimple -d /sys/devices/platform/scx200_gpio.0/bit_0.20_value
opened /sys/devi[ 5744.104000] platform scx200_gpio.0: nsc_gpio_sysfs_get(V) = 0
ces/platform/scx200_gpio.0/bit_0.20_value, for 1 loops, 1000000 samples
read 1000000 samples in 3.8179 sec, rate: 261926.3556 samples/sec

running: scansimple -d /sys/devices/platform/pc8736x_gpio.0/bit_2.1_value
opened /sys/devi[ 5747.956000] platform pc8736x_gpio.0: nsc_gpio_sysfs_get(V) = 1
ces/platform/pc8736x_gpio.0/bit_2.1_value, for 1 loops, 1000000 samples
read 1000000 samples in 3.5392 sec, rate: 282549.9708 samples/sec


Q1- do the device-file read rates look reasonable ?

217k reads/sec, to the led ( a device-file to scx200-gpio )
153k reads/sec to pc8736x_gpio

# cat /proc/cpuinfo
processor : 0
vendor_id : Geode by NSC
cpu family : 5
model : 9
model name : Unknown
stepping : 1
cpu MHz : 266.648

Roughly, I think this looks ok, the scx is on-chip gpio, on pci interface, other is isa bus.
However, I dont have experience to compare it against.


Q2 - my sysfs interface looks broken ?

- its too fast
250k reads/sec to sysfs-interface to pc8736x_gpio.
277k to scx200_gpio

- its evidently not re-reading the pin, this appears just once, not repeatedly.
[ 3534.552000] platform scx200_gpio.0: nsc_gpio_sysfs_get(V) = 1


Q3 - why does cat /dev/led read forever ?
A- no EOF, and cat re-reads til EOF.
also - no newline, read() length always 1 (later-Q)


Q4 - I added an lseek call to scansimple.

it slows down the read-rate. (about 1/2 speed)
it *doesnt* seem to re-trigger the underlying accessor.

running: scansimple -b -d /sys/devices/platform/scx200_gpio.0/bit_0.20_value
opened /sys/devi[ 6117.828000] platform scx200_gpio.0: nsc_gpio_sysfs_get(V) = 0
ces/platform/scx200_gpio.0/bit_0.20_value, for 1 loops, 1000000 samples
read 1000000 samples in 7.2937 sec, rate: 137104.5667 samples/sec

running: scansimple -b -d /sys/devices/platform/pc8736x_gpio.0/bit_2.1_value
opened /sys/devi[ 6125.160000] platform pc8736x_gpio.0: nsc_gpio_sysfs_get(V) = 1
ces/platform/pc8736x_gpio.0/bit_2.1_value, for 1 loops, 1000000 samples
read 1000000 samples in 7.1696 sec, rate: 139476.9601 samples/sec

Q5 - should I be looking for pollable sysfs ?
If so, can someone recommend a best-practice, nearest-fit module I can review ?

Q6 - my sysfs callbacks are appending newline.
- cargo cult (just copying from somewhere)
- is it proper ? (looking for commonality)

Q7 - WRT gpio_ops, Ive s/u8/u32/ most params & returntypes, assuming that
- is cost-less - (all stack args are full word, never byte)
- casting up from u8 to u32 is fine. (no baroque type games)

Q8 - its tempting to add a 'command' attr,
- which responds to writes identically to the device-file interface
- reads return the string produced by gpio_dump.
- defensible as a compatibility-hack (or not?)


tia
- jimc

ps - theres (no doubt) plenty thats not working..


diff -ruNp -X dontdiff -X exclude-diffs ag-0/drivers/char/nsc_gpio.c ag-1/drivers/char/nsc_gpio.c
--- ag-0/drivers/char/nsc_gpio.c 2006-07-15 01:08:50.000000000 -0600
+++ ag-1/drivers/char/nsc_gpio.c 2006-07-18 09:11:12.000000000 -0600
@@ -13,10 +13,12 @@
#include <linux/errno.h>
#include <linux/kernel.h>
#include <linux/init.h>
+#include <linux/hwmon.h>
+#include <linux/hwmon-sysfs.h>
#include <linux/nsc_gpio.h>
#include <linux/platform_device.h>
+#include <linux/io.h>
#include <asm/uaccess.h>
-#include <asm/io.h>

#define NAME "nsc_gpio"

@@ -39,62 +41,79 @@ void nsc_gpio_dump(struct nsc_gpio_ops *
amp->gpio_get(index), amp->gpio_current(index));
}

+/* the pin-mode-change 'commands' of the legacy device-file-interface,
+ now refactored for reuse by a (tbd) sysfs-interface. Includes some
+ updates which arent (currently;-) exposed via sysfs.
+*/
+static int common_write (struct nsc_gpio_ops *amp, char c, unsigned m)
+{
+ struct device *dev = amp->dev;
+ int err = 0;
+ switch (c) {
+ case '0':
+ amp->gpio_set(m, 0);
+ break;
+ case '1':
+ amp->gpio_set(m, 1);
+ break;
+ case 'O':
+ dev_dbg(dev, "GPIO%d output enabled\n", m);
+ amp->gpio_config(m, ~1, 1);
+ break;
+ case 'o':
+ dev_dbg(dev, "GPIO%d output disabled\n", m);
+ amp->gpio_config(m, ~1, 0);
+ break;
+ case 'T':
+ dev_dbg(dev, "GPIO%d output is push pull\n", m);
+ amp->gpio_config(m, ~2, 2);
+ break;
+ case 't':
+ dev_dbg(dev, "GPIO%d output is open drain\n", m);
+ amp->gpio_config(m, ~2, 0);
+ break;
+ case 'P':
+ dev_dbg(dev, "GPIO%d pull up enabled\n", m);
+ amp->gpio_config(m, ~4, 4);
+ break;
+ case 'p':
+ dev_dbg(dev, "GPIO%d pull up disabled\n", m);
+ amp->gpio_config(m, ~4, 0);
+ break;
+
+ case 'v':
+ /* View Current pin settings */
+ amp->gpio_dump(amp, m);
+ break;
+ case 'c':
+ /* view pin's current values: driven and read */
+ dev_info(dev, "io%02d: driven %d, input %d\n",
+ m, amp->gpio_current(m), amp->gpio_get(m));
+ break;
+ case '\n':
+ /* end of settings string, do nothing */
+ break;
+ default:
+ dev_err(dev, "GPIO-%2d bad setting: chr<0x%2x>\n", m,
+ (int)c);
+ err++;
+ }
+ return err;
+}
+
ssize_t nsc_gpio_write(struct file *file, const char __user *data,
size_t len, loff_t *ppos)
{
unsigned m = iminor(file->f_dentry->d_inode);
struct nsc_gpio_ops *amp = file->private_data;
- struct device *dev = amp->dev;
- size_t i;
- int err = 0;
+ int i, err = 0;

for (i = 0; i < len; ++i) {
char c;
if (get_user(c, data + i))
return -EFAULT;
- switch (c) {
- case '0':
- amp->gpio_set(m, 0);
- break;
- case '1':
- amp->gpio_set(m, 1);
- break;
- case 'O':
- dev_dbg(dev, "GPIO%d output enabled\n", m);
- amp->gpio_config(m, ~1, 1);
- break;
- case 'o':
- dev_dbg(dev, "GPIO%d output disabled\n", m);
- amp->gpio_config(m, ~1, 0);
- break;
- case 'T':
- dev_dbg(dev, "GPIO%d output is push pull\n", m);
- amp->gpio_config(m, ~2, 2);
- break;
- case 't':
- dev_dbg(dev, "GPIO%d output is open drain\n", m);
- amp->gpio_config(m, ~2, 0);
- break;
- case 'P':
- dev_dbg(dev, "GPIO%d pull up enabled\n", m);
- amp->gpio_config(m, ~4, 4);
- break;
- case 'p':
- dev_dbg(dev, "GPIO%d pull up disabled\n", m);
- amp->gpio_config(m, ~4, 0);
- break;
- case 'v':
- /* View Current pin settings */
- amp->gpio_dump(amp, m);
- break;
- case '\n':
- /* end of settings string, do nothing */
- break;
- default:
- dev_err(dev, "io%2d bad setting: chr<0x%2x>\n",
- m, (int)c);
- err++;
- }
+
+ err += common_write(amp, c, m);
}
if (err)
return -EINVAL; /* full string handled, report error */
@@ -102,7 +121,7 @@ ssize_t nsc_gpio_write(struct file *file
return len;
}

-ssize_t nsc_gpio_read(struct file *file, char __user * buf,
+ssize_t nsc_gpio_read(struct file *file, char __user *buf,
size_t len, loff_t * ppos)
{
unsigned m = iminor(file->f_dentry->d_inode);
@@ -121,8 +140,121 @@ EXPORT_SYMBOL(nsc_gpio_write);
EXPORT_SYMBOL(nsc_gpio_read);
EXPORT_SYMBOL(nsc_gpio_dump);

+/* now sysfs versions. We use separate read and write callbacks,
+ which use 2D addressing, on index & nr, to select the bit-numbers
+ and pin-features
+ Slightly complicating things, these declarations must be made in
+ the client modules of this one, ie scx200 & pc8736x _gpio. They
+ also must set device.driver_data to amp, as thats needed by
+ sysfs_set_value()
+*/
+ssize_t nsc_gpio_sysfs_set(struct device *dev,
+ struct device_attribute *devattr, const char *buf,
+ size_t count)
+{
+ struct sensor_device_attribute_2 *attr = to_sensor_dev_attr_2(devattr);
+ int idx = attr->index;
+ int func = attr->nr;
+ struct nsc_gpio_ops *amp = dev->driver_data;
+
+ int err = common_write(amp, func, idx);
+ if (err)
+ return -EINVAL; /* full string handled, report error */
+
+ return strlen(buf);
+}
+
+ssize_t nsc_gpio_sysfs_get(struct device *dev,
+ struct device_attribute *devattr, char *buf)
+{ + struct sensor_device_attribute_2 *attr = to_sensor_dev_attr_2(devattr);
+ int idx = attr->index;
+ int func = attr->nr;
+ unsigned res = -1;
+ u32 config;
+ struct nsc_gpio_ops *amp = dev->driver_data;
+
+ if (!amp) {
+ dev_err(dev, "nsc_gpio_sysfs_get(), no amp\n");
+ return 0;
+ }
+ /* fetch value - fast path */
+ if (func == 'V') {
+ res = amp->gpio_get(idx);
+ dev_warn(dev, "nsc_gpio_sysfs_get(V) = %d\n", res);
+ return sprintf(buf, "%u\n", !!res);
+ }
+
+ config = amp->gpio_config(idx, ~0, 0);
+
+ dev_dbg(dev, "nsc_gpio_sysfs_get(), bitconf=%02x, cmd='%c'\n",
+ config, func);
+
+ switch ((char)func) {
+ case 'V':
+ dev_warn(dev, "shouldnt get here (V) = %d, %d\n", res, config);
+ break;
+ case 'O':
+ res = config & 1;
+ break;
+ case 'P':
+ res = config & 2;
+ break;
+ case 'T':
+ res = config & 4;
+ break;
+ case 'L':
+ res = config & 8;
+ break;
+ case 'D':
+ res = config & PF_DEBOUNCE;
+ break;
+
+ default:
+ dev_err(dev, "unknown cmd '%c'\n", func);
+ }
+ dev_dbg(dev, "bit[%d].cmd('%c')\n", idx, func);
+
+ return sprintf(buf, "%u\n", !!res);
+}
+
+EXPORT_SYMBOL_GPL(nsc_gpio_sysfs_get);
+EXPORT_SYMBOL_GPL(nsc_gpio_sysfs_set);
+
+void nsc_gpio_sysfs_port_init(struct device* dev,
+ struct gpio_attributes pp[],
+ int numdevs)
+{
+ int i, err = 0;
+
+ for (i = 0; i < numdevs; i++) {
+
+ err = device_create_file(dev, &pp[i].value.dev_attr);
+ if (err) dev_err(dev, "got err %d\n", err);
+
+ err = device_create_file(dev, &pp[i].output_enabled.dev_attr);
+ if (err) dev_err(dev, "got err %d\n", err);
+
+ err = device_create_file(dev, &pp[i].pullup_enabled.dev_attr);
+ if (err) dev_err(dev, "got err %d\n", err);
+
+ err = device_create_file(dev, &pp[i].totem_pole.dev_attr);
+ if (err) dev_err(dev, "got err %d\n", err);
+
+ err = device_create_file(dev, &pp[i].locked.dev_attr);
+ if (err) dev_err(dev, "got err %d\n", err);
+
+ err = device_create_file(dev, &pp[i].debounced.dev_attr);
+ if (err) dev_err(dev, "got err %d\n", err);
+
+ }
+}
+
+EXPORT_SYMBOL_GPL(nsc_gpio_sysfs_port_init);
+
static int __init nsc_gpio_init(void)
{
+ // struct device *dev;
printk(KERN_DEBUG NAME " initializing\n");
return 0;
}
diff -ruNp -X dontdiff -X exclude-diffs ag-0/drivers/char/pc8736x_gpio.c ag-1/drivers/char/pc8736x_gpio.c
--- ag-0/drivers/char/pc8736x_gpio.c 2006-07-15 01:08:50.000000000 -0600
+++ ag-1/drivers/char/pc8736x_gpio.c 2006-07-18 09:21:19.000000000 -0600
@@ -18,6 +18,8 @@
#include <linux/io.h>
#include <linux/ioport.h>
#include <linux/mutex.h>
+#include <linux/hwmon.h>
+#include <linux/hwmon-sysfs.h>
#include <linux/nsc_gpio.h>
#include <linux/platform_device.h>
#include <asm/uaccess.h>
@@ -32,6 +34,14 @@ static int major; /* default to dynamic
module_param(major, int, 0);
MODULE_PARM_DESC(major, "Major device number");

+static int nobits = 0;
+module_param(nobits, int, 0);
+MODULE_PARM_DESC(nobits, "nobits=1 to suppress sysfs bits interface");
+
+static int noports = 0;
+module_param(noports, int, 0);
+MODULE_PARM_DESC(noports, "noports=1 to supress sysfs ports interface");
+
static DEFINE_MUTEX(pc8736x_gpio_config_lock);
static unsigned pc8736x_gpio_base;
static u8 pc8736x_gpio_shadow[4];
@@ -144,7 +154,7 @@ static u32 pc8736x_gpio_configure(unsign
SIO_GPIO_PIN_CONFIG);
}

-static int pc8736x_gpio_get(unsigned minor)
+static u32 pc8736x_gpio_get(unsigned minor)
{
int port, bit, val;

@@ -188,16 +198,6 @@ static void pc8736x_gpio_set(unsigned mi
pc8736x_gpio_shadow[port] = val;
}

-static void pc8736x_gpio_set_high(unsigned index)
-{
- pc8736x_gpio_set(index, 1);
-}
-
-static void pc8736x_gpio_set_low(unsigned index)
-{
- pc8736x_gpio_set(index, 0);
-}
-
static int pc8736x_gpio_current(unsigned minor)
{
int port, bit;
@@ -212,6 +212,37 @@ static void pc8736x_gpio_change(unsigned
pc8736x_gpio_set(index, !pc8736x_gpio_current(index));
}

+static u32 pc8736x_gpio_get_port(unsigned port)
+{
+ u8 val = inb_p(pc8736x_gpio_base + port_offset[port] + PORT_IN);
+
+ dev_dbg(&pdev->dev, "_gpio_get(%d from %x) == val %d\n",
+ port, pc8736x_gpio_base + port_offset[port] + PORT_IN,
+ val);
+
+ return (u32)val;
+}
+
+static void pc8736x_gpio_set_port(unsigned port, u32 bits, u32 mask)
+{
+ u8 val;
+ u8 curval = inb_p(pc8736x_gpio_base + port_offset[port] + PORT_OUT);
+
+ val = (curval & ~mask) | (bits & mask);
+
+ dev_dbg(&pdev->dev, "gpio_set(port:%d addr:%x cur:%x new:%d)\n",
+ port, pc8736x_gpio_base + port_offset[port] + PORT_OUT,
+ curval, val);
+
+ outb_p(val, pc8736x_gpio_base + port_offset[port] + PORT_OUT);
+
+ curval = inb_p(pc8736x_gpio_base + port_offset[port] + PORT_OUT);
+ val = inb_p(pc8736x_gpio_base + port_offset[port] + PORT_IN);
+
+ dev_dbg(&pdev->dev, "wrote %x, read: %x\n", curval, val);
+ pc8736x_gpio_shadow[port] = val;
+}
+
static struct nsc_gpio_ops pc8736x_gpio_ops = {
.owner = THIS_MODULE,
.gpio_config = pc8736x_gpio_configure,
@@ -219,7 +250,9 @@ static struct nsc_gpio_ops pc8736x_gpio_
.gpio_get = pc8736x_gpio_get,
.gpio_set = pc8736x_gpio_set,
.gpio_change = pc8736x_gpio_change,
- .gpio_current = pc8736x_gpio_current
+ .gpio_current = pc8736x_gpio_current,
+ .gpio_get_port = pc8736x_gpio_get_port,
+ .gpio_set_port = pc8736x_gpio_set_port,
};
EXPORT_SYMBOL(pc8736x_gpio_ops);

@@ -242,16 +275,52 @@ static const struct file_operations pc87
.read = nsc_gpio_read,
};

+static struct gpio_attributes port0[] = {
+ GPIO_ATTRS(0,0), GPIO_ATTRS(0,1), GPIO_ATTRS(0,2), GPIO_ATTRS(0,3),
+ GPIO_ATTRS(0,4), GPIO_ATTRS(0,5), GPIO_ATTRS(0,6), GPIO_ATTRS(0,7)
+};
+static struct gpio_attributes port1[] = {
+ GPIO_ATTRS(1,0), GPIO_ATTRS(1,1), GPIO_ATTRS(1,2), GPIO_ATTRS(1,3),
+ GPIO_ATTRS(1,4), GPIO_ATTRS(1,5), GPIO_ATTRS(1,6), GPIO_ATTRS(1,7)
+};
+static struct gpio_attributes port2[] = {
+ GPIO_ATTRS(2,0), GPIO_ATTRS(2,1), GPIO_ATTRS(2,2), GPIO_ATTRS(2,3),
+ GPIO_ATTRS(2,4), GPIO_ATTRS(2,5), GPIO_ATTRS(2,6), GPIO_ATTRS(2,7)
+};
+static struct gpio_attributes port3[] = {
+ GPIO_ATTRS(3,0), GPIO_ATTRS(3,1), GPIO_ATTRS(3,2), GPIO_ATTRS(3,3),
+ GPIO_ATTRS(3,4), GPIO_ATTRS(3,5), GPIO_ATTRS(3,6), GPIO_ATTRS(3,7)
+};
+
+static struct gpio_attributes ports[] = {
+ GPIO_PORT_ATTRS(0), GPIO_PORT_ATTRS(1),
+ GPIO_PORT_ATTRS(2), GPIO_PORT_ATTRS(3),
+};
+
+static void __init pc8736x_sysfs_init(struct device* dev)
+{
+ dev_info(dev, "pc8736x_sysfs_init creating pin-attr nodes\n");
+ if (!nobits) {
+ nsc_gpio_sysfs_port_init(&pdev->dev, port0, 8);
+ nsc_gpio_sysfs_port_init(&pdev->dev, port1, 8);
+ nsc_gpio_sysfs_port_init(&pdev->dev, port2, 8);
+ nsc_gpio_sysfs_port_init(&pdev->dev, port3, 8);
+ }
+ if (!noports) {
+ dev_info(dev, "creating gpio port-attrs\n");
+ nsc_gpio_sysfs_port_init(&pdev->dev, ports, 4);
+ }
+}
+
static void __init pc8736x_init_shadow(void)
{
int port;
-
+
/* read the current values driven on the GPIO signals */
for (port = 0; port < 4; ++port)
pc8736x_gpio_shadow[port]
- = inb_p(pc8736x_gpio_base + port_offset[port]
- + PORT_OUT);
-
+ = inb_p(pc8736x_gpio_base + port_offset[port]
+ + PORT_OUT);
}

static struct cdev pc8736x_gpio_cdev;
@@ -330,6 +399,11 @@ static int __init pc8736x_gpio_init(void
cdev_init(&pc8736x_gpio_cdev, &pc8736x_gpio_fileops);
cdev_add(&pc8736x_gpio_cdev, devid, PC8736X_GPIO_CT);

+ pc8736x_sysfs_init(&pdev->dev);
+
+ /* provide info wheresysfs callbacks can get them */
+ pc8736x_gpio_ops.dev->driver_data = &pc8736x_gpio_ops;
+
return 0;

undo_request_region:
diff -ruNp -X dontdiff -X exclude-diffs ag-0/drivers/char/scx200_gpio.c ag-1/drivers/char/scx200_gpio.c
--- ag-0/drivers/char/scx200_gpio.c 2006-07-15 01:08:50.000000000 -0600
+++ ag-1/drivers/char/scx200_gpio.c 2006-07-18 09:21:25.000000000 -0600
@@ -33,6 +33,14 @@ static int major = 0; /* default to dyn
module_param(major, int, 0);
MODULE_PARM_DESC(major, "Major device number");

+static int nobits = 0;
+module_param(nobits, int, 0);
+MODULE_PARM_DESC(nobits, "nobits=1 to suppress sysfs bits interface");
+
+static int noports = 0;
+module_param(noports, int, 0);
+MODULE_PARM_DESC(noports, "noports=1 to supress sysfs ports interface");
+
#define MAX_PINS 32 /* 64 later, when known ok */

struct nsc_gpio_ops scx200_gpio_ops = {
@@ -42,9 +50,11 @@ struct nsc_gpio_ops scx200_gpio_ops = {
.gpio_get = scx200_gpio_get,
.gpio_set = scx200_gpio_set,
.gpio_change = scx200_gpio_change,
- .gpio_current = scx200_gpio_current
+ .gpio_current = scx200_gpio_current,
+ .gpio_get_port = scx200_gpio_get_port,
+ .gpio_set_port = scx200_gpio_set_port,
};
-EXPORT_SYMBOL(scx200_gpio_ops);
+EXPORT_SYMBOL_GPL(scx200_gpio_ops);

static int scx200_gpio_open(struct inode *inode, struct file *file)
{
@@ -69,7 +79,45 @@ static const struct file_operations scx2
.release = scx200_gpio_release,
};

-struct cdev scx200_gpio_cdev; /* use 1 cdev for all pins */
+static struct cdev scx200_gpio_cdev; /* use 1 cdev for all pins */
+
+/* insert sysfs decl and init-func here */
+static struct gpio_attributes port0[] = {
+ GPIO_ATTRS(0,0), GPIO_ATTRS(0,1), GPIO_ATTRS(0,2), GPIO_ATTRS(0,3),
+ GPIO_ATTRS(0,4), GPIO_ATTRS(0,5), GPIO_ATTRS(0,6), GPIO_ATTRS(0,7),
+ GPIO_ATTRS(0,8), GPIO_ATTRS(0,9), GPIO_ATTRS(0,10), GPIO_ATTRS(0,11),
+ GPIO_ATTRS(0,12), GPIO_ATTRS(0,13), GPIO_ATTRS(0,14), GPIO_ATTRS(0,15),
+
+ GPIO_ATTRS(0,16), GPIO_ATTRS(0,17), GPIO_ATTRS(0,18), GPIO_ATTRS(0,19),
+ GPIO_ATTRS(0,20), GPIO_ATTRS(0,21), GPIO_ATTRS(0,22), GPIO_ATTRS(0,23),
+ GPIO_ATTRS(0,24), GPIO_ATTRS(0,25), GPIO_ATTRS(0,26), GPIO_ATTRS(0,27),
+ GPIO_ATTRS(0,28), GPIO_ATTRS(0,29), GPIO_ATTRS(0,30), GPIO_ATTRS(0,31),
+};
+
+static struct gpio_attributes port1[] = {
+ GPIO_ATTRS(1,0), GPIO_ATTRS(1,1), GPIO_ATTRS(1,2), GPIO_ATTRS(1,3),
+ GPIO_ATTRS(1,4), GPIO_ATTRS(1,5), GPIO_ATTRS(1,6), GPIO_ATTRS(1,7),
+ GPIO_ATTRS(1,8), GPIO_ATTRS(1,9), GPIO_ATTRS(1,10), GPIO_ATTRS(1,11),
+ GPIO_ATTRS(1,12), GPIO_ATTRS(1,13), GPIO_ATTRS(1,14), GPIO_ATTRS(1,15),
+
+ GPIO_ATTRS(1,16), GPIO_ATTRS(1,17), GPIO_ATTRS(1,18), GPIO_ATTRS(1,19),
+ GPIO_ATTRS(1,20), GPIO_ATTRS(1,21), GPIO_ATTRS(1,22), GPIO_ATTRS(1,23),
+ GPIO_ATTRS(1,24), GPIO_ATTRS(1,25), GPIO_ATTRS(1,26), GPIO_ATTRS(1,27),
+ GPIO_ATTRS(1,28), GPIO_ATTRS(1,29), GPIO_ATTRS(1,30), GPIO_ATTRS(1,31),
+};
+static struct gpio_attributes ports[] = {
+ GPIO_PORT_ATTRS(0), GPIO_PORT_ATTRS(1),
+};
+
+static void __init scx200_sysfs_init(struct device* dev)
+{
+ if (!nobits) {
+ nsc_gpio_sysfs_port_init(dev, port0, 32);
+ nsc_gpio_sysfs_port_init(dev, port1, 32);
+ }
+ if (!noports)
+ nsc_gpio_sysfs_port_init(dev, ports, 2);
+}

static int __init scx200_gpio_init(void)
{
@@ -92,6 +140,7 @@ static int __init scx200_gpio_init(void)

/* nsc_gpio uses dev_dbg(), so needs this */
scx200_gpio_ops.dev = &pdev->dev;
+ scx200_gpio_ops.dev->driver_data = &scx200_gpio_ops;

if (major) {
devid = MKDEV(major, 0);
@@ -108,6 +157,8 @@ static int __init scx200_gpio_init(void)
cdev_init(&scx200_gpio_cdev, &scx200_gpio_fileops);
cdev_add(&scx200_gpio_cdev, devid, MAX_PINS);

+ scx200_sysfs_init(&pdev->dev);
+
return 0; /* succeed */

undo_platform_device_add:
diff -ruNp -X dontdiff -X exclude-diffs ag-0/drivers/leds/leds-net48xx.c ag-1/drivers/leds/leds-net48xx.c
--- ag-0/drivers/leds/leds-net48xx.c 2006-07-15 01:08:58.000000000 -0600
+++ ag-1/drivers/leds/leds-net48xx.c 2006-07-17 13:28:49.000000000 -0600
@@ -16,6 +16,7 @@
#include <linux/leds.h>
#include <linux/err.h>
#include <asm/io.h>
+#include <linux/nsc_gpio.h>
#include <linux/scx200_gpio.h>

#define DRVNAME "net48xx-led"
@@ -26,10 +27,7 @@ static struct platform_device *pdev;
static void net48xx_error_led_set(struct led_classdev *led_cdev,
enum led_brightness value)
{
- if (value)
- scx200_gpio_set_high(NET48XX_ERROR_LED_GPIO);
- else
- scx200_gpio_set_low(NET48XX_ERROR_LED_GPIO);
+ scx200_gpio_ops.gpio_set(NET48XX_ERROR_LED_GPIO, value ? 1 : 0);
}

static struct led_classdev net48xx_error_led = {
@@ -81,7 +79,8 @@ static int __init net48xx_led_init(void)
{
int ret;

- if (!scx200_gpio_present()) {
+ /* small hack, but scx200_gpio doesn't set .dev if the probe fails */
+ if (!scx200_gpio_ops.dev) {
ret = -ENODEV;
goto out;
}
diff -ruNp -X dontdiff -X exclude-diffs ag-0/include/linux/nsc_gpio.h ag-1/include/linux/nsc_gpio.h
--- ag-0/include/linux/nsc_gpio.h 2006-07-15 01:09:44.000000000 -0600
+++ ag-1/include/linux/nsc_gpio.h 2006-07-18 09:17:30.000000000 -0600
@@ -1,6 +1,4 @@
/**
- nsc_gpio.c
-
National Semiconductor GPIO common access methods.

struct nsc_gpio_ops abstracts the low-level access
@@ -19,17 +17,31 @@
NSC sold the GEODE line to AMD, and the PC-8736x line to Winbond.
*/

+/* pin-feature to config-bit mapping is common to both chips
+ some ports' pins dont support upper nibble ops.
+*/
+#define PF_OUTPUT_ENA 1 /* !tristate */
+#define PF_TOTEM 2 /* !open-drain */
+#define PF_PULLUP 4
+#define PF_LOCKED 8
+#define PF_INTERRUPT_ENA 16
+#define PF_INTERRUPT_TGR 32
+#define PF_DEBOUNCE 64
+
struct nsc_gpio_ops {
struct module* owner;
- u32 (*gpio_config) (unsigned iminor, u32 mask, u32 bits);
- void (*gpio_dump) (struct nsc_gpio_ops *amp, unsigned iminor);
- int (*gpio_get) (unsigned iminor);
- void (*gpio_set) (unsigned iminor, int state);
- void (*gpio_change) (unsigned iminor);
- int (*gpio_current) (unsigned iminor);
- struct device* dev; /* for dev_dbg() support, set in init */
+ u32 (*gpio_config) (unsigned iminor, u32 mask, u32 bits);
+ void (*gpio_dump) (struct nsc_gpio_ops *amp, unsigned iminor);
+ u32 (*gpio_get) (unsigned iminor);
+ void (*gpio_set) (unsigned iminor, int state);
+ void (*gpio_change) (unsigned iminor);
+ int (*gpio_current) (unsigned iminor);
+ u32 (*gpio_get_port) (unsigned port);
+ void (*gpio_set_port) (unsigned port, u32 bits, u32 mask);
+ struct device* dev; /* for dev_dbg() support */
};

+/* fileops routines */
extern ssize_t nsc_gpio_write(struct file *file, const char __user *data,
size_t len, loff_t *ppos);

@@ -38,3 +50,95 @@ extern ssize_t nsc_gpio_read(struct file

extern void nsc_gpio_dump(struct nsc_gpio_ops *amp, unsigned index);

+
+/* the 'bits' sysfs interface uses a single/combo callback function
+ which does 2D indexing; on index & nr, selecting bit number and
+ pin-feature/attribute
+*/
+extern ssize_t nsc_gpio_sysfs_get(struct device *dev,
+ struct device_attribute *devattr,
+ char *buf);
+
+extern ssize_t nsc_gpio_sysfs_set(struct device *dev,
+ struct device_attribute *devattr,
+ const char *buf, size_t count);
+
+#include <linux/hwmon.h>
+#include <linux/hwmon-sysfs.h>
+
+struct gpio_attributes {
+ struct sensor_device_attribute_2 value;
+ struct sensor_device_attribute_2 curr;
+ struct sensor_device_attribute_2 output_enabled;
+ struct sensor_device_attribute_2 totem_pole;
+ struct sensor_device_attribute_2 pullup_enabled;
+ struct sensor_device_attribute_2 debounced;
+ struct sensor_device_attribute_2 locked;
+};
+
+#define GPIO_PIN(_grp, _idx, _pre, _post, _mode, _show, _store, _nr) \
+ { .dev_attr = __ATTR(_pre## _grp._idx ##_post, \
+ _mode, _show, _store), \
+ .index = _idx, .nr = _nr }
+
+#define GPIO_ATTR(Portnum, Bitnum, FnSym, AttrNm) \
+ GPIO_PIN(Portnum, Bitnum, bit_, AttrNm, \
+ S_IWUSR | S_IRUGO, \
+ nsc_gpio_sysfs_get, nsc_gpio_sysfs_set, FnSym )
+
+/* The original scx200_gpio driver accessed pins via device-files.
+ and set pin features in response to command-strings written to it.
+*/
+#define PIN_VAL(Port, Bit) GPIO_ATTR(Port, Bit, 'V', _value)
+#define PIN_CURR(Port, Bit) GPIO_ATTR(Port, Bit, 'C', _current_output)
+#define PIN_OE(Port, Bit) GPIO_ATTR(Port, Bit, 'O', _output_enabled)
+#define PIN_PUE(Port, Bit) GPIO_ATTR(Port, Bit, 'P', _pullup_enabled)
+#define PIN_PP(Port, Bit) GPIO_ATTR(Port, Bit, 'T', _totem)
+#define PIN_LOCKED(Port, Bit) GPIO_ATTR(Port, Bit, 'L', _locked)
+#define PIN_DEBOUNCE(Port, Bit) GPIO_ATTR(Port, Bit, 'D', _debounced)
+
+/* initializer for bits, ie pin arrays */
+#define GPIO_ATTRS(Port, Idx) { \
+ .value = PIN_VAL(Port, Idx), \
+ .curr = PIN_CURR(Port, Idx), \
+ .output_enabled = PIN_OE(Port, Idx), \
+ .pullup_enabled = PIN_PUE(Port, Idx), \
+ .totem_pole = PIN_PP(Port, Idx), \
+ .locked = PIN_LOCKED(Port, Idx), \
+ .debounced = PIN_DEBOUNCE(Port, Idx) }
+
+extern void nsc_gpio_sysfs_port_init(struct device* dev,
+ struct gpio_attributes pp[],
+ int numdevs);
+
+
+
+/* port-wide sysfs access */
+
+#define GPIO_PORT(_grp, _pre, _post, _mode, _show, _store, _nr) \
+ { .dev_attr = __ATTR(_pre## _grp ##_post, \
+ _mode, _show, _store), \
+ .index = _grp, .nr = _nr }
+
+#define GPIO_PORT_ATTR(Portnum, FnSym, AttrNm) \
+ GPIO_PORT(Portnum, port_, AttrNm, \
+ S_IWUSR | S_IRUGO, \
+ nsc_gpio_sysfs_get, nsc_gpio_sysfs_set, FnSym )
+
+#define PORT_VAL(Port) GPIO_PORT_ATTR(Port, 'V', _value)
+#define PORT_CURR(Port) GPIO_PORT_ATTR(Port, 'C', _current_output)
+#define PORT_OE(Port) GPIO_PORT_ATTR(Port, 'O', _output_enabled)
+#define PORT_PUE(Port) GPIO_PORT_ATTR(Port, 'P', _pullup_enabled)
+#define PORT_PP(Port) GPIO_PORT_ATTR(Port, 'T', _totem)
+#define PORT_LOCKED(Port) GPIO_PORT_ATTR(Port, 'L', _locked)
+#define PORT_DEBOUNCE(Port) GPIO_PORT_ATTR(Port, 'D', _debounced)
+
+#define GPIO_PORT_ATTRS(Port) { \
+ .value = PORT_VAL(Port), \
+ .curr = PORT_CURR(Port), \
+ .output_enabled = PORT_OE(Port), \
+ .pullup_enabled = PORT_PUE(Port), \
+ .totem_pole = PORT_PP(Port), \
+ .locked = PORT_LOCKED(Port), \
+ .debounced = PORT_DEBOUNCE(Port) }
+
diff -ruNp -X dontdiff -X exclude-diffs ag-0/include/linux/scx200_gpio.h ag-1/include/linux/scx200_gpio.h
--- ag-0/include/linux/scx200_gpio.h 2006-07-06 13:20:28.000000000 -0600
+++ ag-1/include/linux/scx200_gpio.h 2006-07-17 23:04:38.000000000 -0600
@@ -4,6 +4,7 @@ u32 scx200_gpio_configure(unsigned index

extern unsigned scx200_gpio_base;
extern long scx200_gpio_shadow[2];
+extern struct nsc_gpio_ops scx200_gpio_ops;

#define scx200_gpio_present() (scx200_gpio_base!=0)

@@ -82,6 +83,26 @@ static inline void scx200_gpio_change(un
__SCx200_GPIO_OUT;
}

+/* return the value of a whole port (bank) */
+static inline u32 scx200_gpio_get_port(unsigned port)
+{
+ unsigned bank = port & 0x1f;
+ __SCx200_GPIO_IOADDR + 0x04;
+
+ return inl(ioaddr);
+}
+
+/* return the value of a whole port (bank) */
+static inline void scx200_gpio_set_port(unsigned port, u32 bits, u32 mask)
+{
+ unsigned bank = port & 0x1f;
+ __SCx200_GPIO_IOADDR;
+ __SCx200_GPIO_SHADOW;
+ *shadow = (*shadow & ~mask) | (bits & mask);
+ __SCx200_GPIO_OUT;
+}
+
+
#undef __SCx200_GPIO_BANK
#undef __SCx200_GPIO_IOADDR
#undef __SCx200_GPIO_SHADOW


-
To unsubscribe from this list: send the line "unsubscribe linux-kernel" in
the body of a message to majordomo@xxxxxxxxxxxxxxx
More majordomo info at http://vger.kernel.org/majordomo-info.html
Please read the FAQ at http://www.tux.org/lkml/