All of lore.kernel.org
 help / color / mirror / Atom feed
* Re: [RFC] The driver model, I2C and gpio provision on Sharp SL-C1000 (Akita)
@ 2005-11-03  7:06 David Brownell
  2005-11-03  9:07 ` Richard Purdie
  0 siblings, 1 reply; 8+ messages in thread
From: David Brownell @ 2005-11-03  7:06 UTC (permalink / raw)
  To: Linux Kernel list; +Cc: andy, Richard Purdie

> > It seems that making i2c init early is only sane choice. I realize PC people
> > will hate it...

What do you mean by "early"?  Other than maybe linking it earlier
in the drivers/Makefile, and probably running some arch-specific i2c
controller (and certain i2c chip) drivers at subsys_initcall rather
than at device_initcall ... does the 2.6.14 kernel need changes?


> > but apart from that, why is it impractical? 
> 
> FWIW, I have also run into this "I need I2C early in boot, but it's not
> inited until late" on SiByte ...

Likewise on many OMAP boards, which tend to have the power management
and other essential features on I2C.

Some board-specific init code ends up needing to run after the probe()
logic of the tps6501x driver ... like for example, using a GPIO (!)
there to power up the Ethernet controller which may be needed for the
root filesystem.  (At least on many development systems!)

You can see where that leads:  we patched the i2c subsystem so that
it runs at subsys_initcall ... and also the omap_i2c driver, and
the tps65010 driver.  No other drivers need to be changed, just the
ones involved in that board's power management.


Richard:

> I had to turn akita-ioexp into a platform device to get the suspend
> signal which is used to flush the workqueue. With a platform device
> available at machine init time, I can insert it as a parent of the corgi
> device chain which means its one of the last devices to be suspended.

By doing all that stuff as "subsys_initcall", the relevant I2C gpio
hardware will be also be suspended "late" ... without such a fake
platform device.  Unless you're doing selective suspend, details of
the device tree matter less than the order used to create devices.

- Dave


^ permalink raw reply	[flat|nested] 8+ messages in thread
* [RFC] The driver model, I2C and gpio provision on Sharp SL-C1000 (Akita)
@ 2005-10-28  9:52 Richard Purdie
  2005-10-28 16:08 ` Russell King
  2005-10-29 19:08 ` Pavel Machek
  0 siblings, 2 replies; 8+ messages in thread
From: Richard Purdie @ 2005-10-28  9:52 UTC (permalink / raw)
  To: LKML, Russell King, Greg KH

This code is the final link is getting akita working but I'm not sure
its the right approach. I'm posting this in the hope someone might see a
better way to achieve this driver's objectives. I'd like to get akita
support into mainline and this is the only barrier.

Background:

The Sharp SL-C1000 (Akita) has an 8 port Maxim 7310 IO Expander
connected via I2C. In this specific case, 5 outputs are used from a
variety of linux subsystems such as the backlight, IR port and sound. 

Driver Design Criteria:

* GPIOs need to be available fairly early in the boot procedure as we
can't guarantee when the subsystems will use them
* GPIOs can be changed from interrupt context
* GPIO providers need to be one of the last devices suspended/first
resumed

A Solution:

Firstly, I wrote an I2C driver for the MAX7310 which is included below.
This shouldn't be too controversial. It does export some functions and
I'm not sure what to do about a header file for them. Creating a
specific header file for them seems a little over the top. I realise I
need to sort out the I2C_DRIVERID properly. I'm also aware it has a lot
of different ids and no method to detect whether the chip is really
present (Akita uses 0x18 as the slave id).

I2C drivers appear relatively late in the boot procedure and changing
that isn't practical. I therefore ended up writing akita-ioexp which
acts as an interface between the rest of the kernel drivers and the
max7310 i2c driver.

When akita-ioexp loads, the i2c driver isn't likely to be present. I've
used the init levels to ensure it comes up after the i2c-core but that's
about all we can hope for. It therefore searches for the max7310 and if
not found, schedules a repeat of the search for later, repeating until
it finds the device (using a workqueue). The workqueue is also used to
get around gpio requests from interrupt context. 

I had to turn akita-ioexp into a platform device to get the suspend
signal which is used to flush the workqueue. With a platform device
available at machine init time, I can insert it as a parent of the corgi
device chain which means its one of the last devices to be suspended. I
couldn't do this with an i2c device as it won't be around at early init.
I do remember reading about the idea of being able to dynamically alter
the device parent chain which would help with this but I don't think
this exists (yet?).

In this case, we only have output gpios and the timing isn't critical.
We can therefore queue the output until the max7310 becomes available.
This wouldn't work for inputs or devices that would have an issue with
delayed output.

Alternatives:

I can see a case for ditching the generic max7310 driver and putting its
code directly into akita-ioexp. If I'd tried this the other way around,
I know what I would have been told :). Anyhow, this solves some issues
and creates others. The main one would be how to arrange for a very late
suspend call as the device wouldn't be available to set as a parent for
the corgi device tree at early boot. Obviously any such driver would
then be totally akita specific.

There is a fundamental problem with the lack of a proper gpio interface
in Linux. Every driver does something different with them (be it pxa
specific gpios, SCOOP gpios, those on a IO expander, those on a video
chip (w100fb springs to mind) to name just the Zaurus specific ones.

Is this patch going to be acceptable and/or is there anything we can do
to improve this driver and/or Linux in general to support these kind of
needs?

Richard

[The full Akita machine patch is available as
http://www.rpsys.net/openzaurus/patches/spitz_base_extras-r4.patch ]

Index: linux-2.6.13/arch/arm/mach-pxa/akita-ioexp.c
===================================================================
--- /dev/null	1970-01-01 00:00:00.000000000 +0000
+++ linux-2.6.13/arch/arm/mach-pxa/akita-ioexp.c	2005-10-27 21:41:13.000000000 +0100
@@ -0,0 +1,118 @@
+/*
+ * Support for the IO Expander on the Sharp SL-C1000 (Akita)
+ *
+ * Copyright 2005 Openedhand Ltd.
+ *
+ * Author: Richard Purdie <richard@openedhand.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/kernel.h>
+#include <linux/init.h>
+#include <linux/device.h>
+#include <linux/i2c.h>
+#include <linux/workqueue.h>
+#include <asm/arch/akita.h>
+
+/* These should be somewhere else */
+int max7310_config(struct device *dev, int iomode, int polarity);
+int max7310_set_ouputs(struct device *dev, int outputs);
+
+static void akita_ioexp_work(void *private_);
+
+static struct device *akita_ioexp_device;
+static unsigned char ioexp_output_value = AKITA_IOEXP_IO_OUT;
+DECLARE_WORK(akita_ioexp, akita_ioexp_work, NULL);
+
+void akita_set_ioexp(struct device *dev, unsigned char bit)
+{
+	ioexp_output_value |= bit;
+
+	if (akita_ioexp_device)
+		schedule_work(&akita_ioexp);
+	return;
+}
+
+void akita_reset_ioexp(struct device *dev, unsigned char bit)
+{
+	ioexp_output_value &= ~bit;
+
+	if (akita_ioexp_device)
+		schedule_work(&akita_ioexp);
+	return;
+}
+
+EXPORT_SYMBOL(akita_set_ioexp);
+EXPORT_SYMBOL(akita_reset_ioexp);
+
+static int is_akita_ioexp_device(struct device *dev, void *data)
+{
+	struct i2c_client *client = to_i2c_client(dev);
+	return (strncmp(client->name, "max7310", 7) == 0);
+}
+
+static void akita_ioexp_work(void *private_)
+{
+	if (!akita_ioexp_device) {
+		akita_ioexp_device = bus_find_device(&i2c_bus_type, NULL, NULL, is_akita_ioexp_device);
+
+		if (!akita_ioexp_device) {
+			/* if we can't find the device, wait and retry */
+			schedule_delayed_work(&akita_ioexp, msecs_to_jiffies(500));
+			return;
+		}
+		
+		max7310_config(akita_ioexp_device, AKITA_IOEXP_IO_DIR, 0);
+	}
+
+	max7310_set_ouputs(akita_ioexp_device, ioexp_output_value);
+}
+
+
+#ifdef CONFIG_PM
+static int akita_ioexp_suspend(struct device *dev, pm_message_t state, uint32_t level)
+{
+	if (level == SUSPEND_POWER_DOWN) {
+		flush_scheduled_work();
+	}
+	return 0;
+}	
+		
+static int akita_ioexp_resume(struct device *dev, uint32_t level)
+{
+	if (level == RESUME_POWER_ON) {
+		schedule_work(&akita_ioexp);
+	}
+	return 0;
+}
+#else
+#define akita_ioexp_suspend NULL
+#define akita_ioexp_resume NULL
+#endif
+
+
+static int __init akita_ioexp_probe(struct device *dev)
+{
+	schedule_work(&akita_ioexp);
+	return 0;
+}
+
+static struct device_driver akita_ioexp_driver = {
+	.name		= "akita-ioexp",
+	.bus		= &platform_bus_type,
+	.probe		= akita_ioexp_probe,
+	.suspend	= akita_ioexp_suspend,
+	.resume		= akita_ioexp_resume,
+};
+
+static int __devinit akita_ioexp_init(void)
+{
+	return driver_register(&akita_ioexp_driver);
+}
+
+module_init(akita_ioexp_init);
+


Index: linux-2.6.13/drivers/i2c/chips/max7310.c
===================================================================
--- /dev/null	1970-01-01 00:00:00.000000000 +0000
+++ linux-2.6.13/drivers/i2c/chips/max7310.c	2005-10-27 21:23:25.000000000 +0100
@@ -0,0 +1,201 @@
+/* 
+ * max7310.c  --  Maxim MAX7310 8 Port IO Expander
+ *
+ * Copyright 2005 Openedhand Ltd.
+ *
+ * Author: Richard Purdie <richard@openedhand.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.
+ */
+
+#define I2C_DRIVERID_MAX7310 0xdafe
+
+#include <linux/module.h>
+#include <linux/init.h>
+#include <linux/slab.h>
+#include <linux/i2c.h>
+
+/* MAX7310 Regiser Map */
+#define MAX7310_INPUT    0x00
+#define MAX7310_OUTPUT   0x01
+#define MAX7310_POLINV   0x02
+#define MAX7310_IODIR    0x03 /* 1 = Input, 0 = Output */
+#define MAX7310_TIMEOUT  0x04
+
+/* Addresses to scan */
+static unsigned short normal_i2c[] = { 
+	0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f,  
+	0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 
+	0x18, 0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f, 
+	0x20, 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, 
+	0x28, 0x29, 0x2a, 0x2b, 0x2c, 0x2d, 0x2e, 0x2f, 
+	0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 
+	0x38, 0x39, 0x3a, 0x3b, 0x3c, 0x3d, 0x3e, 0x3f,
+	I2C_CLIENT_END 
+};
+
+/* I2C Magic */
+I2C_CLIENT_INSMOD;
+
+static int max7310_write(struct i2c_client *client, int address, int data);
+static int max7310_read(struct i2c_client *client, int address);
+static struct i2c_client max7310_template;
+
+/* 
+ * Public Interface
+ */
+int max7310_config(struct device *dev, int iomode, int polarity)
+{
+	int ret;
+	struct i2c_client *client = to_i2c_client(dev);
+
+	ret = max7310_write(client, MAX7310_POLINV, polarity); 
+	if (ret < 0)
+		return ret;
+	ret = max7310_write(client, MAX7310_IODIR, iomode); 
+	return ret;
+}
+
+int max7310_set_ouputs(struct device *dev, int outputs)
+{
+	struct i2c_client *client = to_i2c_client(dev);
+
+	return max7310_write(client, MAX7310_OUTPUT, outputs); 
+}
+
+int max7310_read_inputs(struct device *dev)
+{
+	struct i2c_client *client = to_i2c_client(dev);
+
+	return max7310_read(client, MAX7310_INPUT); 
+}
+
+EXPORT_SYMBOL(max7310_config);
+EXPORT_SYMBOL(max7310_set_ouputs);
+EXPORT_SYMBOL(max7310_read_inputs);
+
+
+/* 
+ * Sysfs Functions 
+ */
+static ssize_t show_gpio(struct device *dev, struct device_attribute *attr, char *buf)
+{
+	return sprintf(buf, "0x%02x\n", max7310_read_inputs(dev));
+}
+
+static ssize_t set_gpio(struct device *dev, struct device_attribute *attr, const char *buf, size_t count)
+{
+	unsigned long val = simple_strtoul(buf, NULL, 16);
+
+	if (val > 0xff)
+		return -EINVAL;
+
+	max7310_set_ouputs(dev, val);
+
+	return count;
+}
+
+static DEVICE_ATTR(gpio, S_IWUSR | S_IRUGO, show_gpio, set_gpio);
+
+
+/*
+ * I2C Functions
+ */
+static int max7310_write(struct i2c_client *client, int address, int value)
+{
+	u8 data[2];
+
+	data[0] = address & 0xff;
+	data[1] = value & 0xff;
+
+	if (i2c_master_send(client, data, 2) == 2)
+		return 0;
+	return -1;
+}
+
+static int max7310_read(struct i2c_client *client, int address)
+{
+	u8 data[1];
+
+	data[0] = address & 0xff;
+
+	if (i2c_master_recv(client, data, 1) == 1) {
+		return data[0];
+	}
+	return -1;
+}
+
+static int max7310_detect(struct i2c_adapter *adapter, int address, int kind)
+{
+	struct i2c_client *new_client;
+	int err;
+
+	if (!(new_client = kmalloc(sizeof(struct i2c_client), GFP_KERNEL)))
+		return -ENOMEM;
+
+	max7310_template.adapter = adapter;
+	max7310_template.addr = address;
+	
+	memcpy(new_client, &max7310_template, sizeof(struct i2c_client));
+
+	if ((err = i2c_attach_client(new_client))) {
+		kfree(new_client);
+		return err;
+	}
+	
+	/* Register sysfs hooks */
+	device_create_file(&new_client->dev, &dev_attr_gpio);
+	return 0;
+}
+
+static int max7310_attach_adapter(struct i2c_adapter *adapter)
+{
+	return i2c_probe(adapter, &addr_data, max7310_detect);
+}
+
+static int max7310_detach_client(struct i2c_client *client)
+{
+	int err;
+
+	device_remove_file(&client->dev, &dev_attr_gpio);
+
+	if ((err = i2c_detach_client(client)))
+		return err;
+
+	kfree(client);
+	return 0;
+}
+
+static struct i2c_driver max7310_i2c_driver = {
+	.owner		= THIS_MODULE,
+	.name		= "max7310",
+	.id		= I2C_DRIVERID_MAX7310,
+	.flags		= I2C_DF_NOTIFY,
+	.attach_adapter	= max7310_attach_adapter,
+	.detach_client	= max7310_detach_client,
+};
+
+static struct i2c_client max7310_template = {
+	name:   "max7310",
+	flags:  I2C_CLIENT_ALLOW_USE,
+	driver: &max7310_i2c_driver,
+};
+
+static int __init max7310_init(void)
+{
+	return i2c_add_driver(&max7310_i2c_driver);
+}
+
+static void __exit max7310_exit(void)
+{
+	i2c_del_driver(&max7310_i2c_driver);
+}
+
+MODULE_AUTHOR("Richard Purdie <rpurdie@openedhand.com>");
+MODULE_DESCRIPTION("MAX7310 driver");
+MODULE_LICENSE("GPL");
+
+module_init(max7310_init);
+module_exit(max7310_exit);
Index: linux-2.6.13/drivers/i2c/chips/Kconfig
===================================================================
--- linux-2.6.13.orig/drivers/i2c/chips/Kconfig	2005-10-25 16:03:08.000000000 +0100
+++ linux-2.6.13/drivers/i2c/chips/Kconfig	2005-10-27 14:47:02.000000000 +0100
@@ -126,4 +126,17 @@
 	  This driver can also be built as a module.  If so, the module
 	  will be called max6875.
 
+config MAX7310
+	tristate "Maxim MAX7310 8 Port IO Exander"
+	depends on I2C && EXPERIMENTAL
+	help
+	  If you say yes here you get support for the Maxim MAX7310
+	  8 Port IO Exander. 
+
+	  This chip is found in certain devices such as the Sharp Zaurus
+	  SL-C1000 (Akita).
+	  
+	  This driver can also be built as a module.  If so, the module
+	  will be called max7310.
+
 endmenu
Index: linux-2.6.13/drivers/i2c/chips/Makefile
===================================================================
--- linux-2.6.13.orig/drivers/i2c/chips/Makefile	2005-08-29 00:41:01.000000000 +0100
+++ linux-2.6.13/drivers/i2c/chips/Makefile	2005-10-27 14:47:14.000000000 +0100
@@ -12,6 +12,7 @@
 obj-$(CONFIG_SENSORS_PCF8591)	+= pcf8591.o
 obj-$(CONFIG_SENSORS_RTC8564)	+= rtc8564.o
 obj-$(CONFIG_ISP1301_OMAP)	+= isp1301_omap.o
+obj-$(CONFIG_MAX7310)		+= max7310.o
 obj-$(CONFIG_TPS65010)		+= tps65010.o
 
 ifeq ($(CONFIG_I2C_DEBUG_CHIP),y)





^ permalink raw reply	[flat|nested] 8+ messages in thread

end of thread, other threads:[~2005-11-03 15:38 UTC | newest]

Thread overview: 8+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2005-11-03  7:06 [RFC] The driver model, I2C and gpio provision on Sharp SL-C1000 (Akita) David Brownell
2005-11-03  9:07 ` Richard Purdie
2005-11-03 15:38   ` David Brownell
  -- strict thread matches above, loose matches on Subject: below --
2005-10-28  9:52 Richard Purdie
2005-10-28 16:08 ` Russell King
2005-10-29 19:08 ` Pavel Machek
2005-11-02 19:44   ` Andy Isaacson
2005-11-02 22:52     ` Russell King

This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.