* Fundamental Design Flaw of the Device Driver Model?
@ 2008-08-22 6:25 Eric Miao
2008-08-22 9:03 ` Pavel Machek
` (3 more replies)
0 siblings, 4 replies; 10+ messages in thread
From: Eric Miao @ 2008-08-22 6:25 UTC (permalink / raw)
To: LKML
Fundamental Design Flaw of the Device Driver Model?
===================================================
Sorry for the misleading subject, its purpose is to draw your attention :-)
The ideas below are preliminary and I hope I'm not making serious mistakes
here.
This question has actually been around in my mind for several months, when
I started to work on some devices with multiple functions. Specifically, a
Power Management IC (PMIC in short in the following text) usually includes
LEDs support (charging, indication...) audio, touch screen, power monitoring,
LDOs, DC-DC bucks, and possibly some others.
The initial two ideas came into my mind were:
1. separate the functions into multiple devices, write a driver for each
of these devices
2. a big all-in-one driver
Pros and Cons
Solution (1) is obviously more attractive: better modularization and
structure, work can be incrementally done, individual patches can go
through each subsystem's git tree, reviewed by dedicated people, and
so on.
One thing I'm not so happy with solution (1) is: you have to create
intermediate devices in order that the dedicated sub-device drivers
to match up. E.g.
==== pmic.c =============================================================
...
pmic_add_subdevs(void)
{
for all sub-devices on this pmic:
pdev = platform_device_alloc(sub-device-name, sub-device-id)
platform_device_add(pdev);
...
}
pmic_probe(struct i2c_client *client)
{
...
pmic_add_subdevs();
...
}
struct i2c_driver pmic_driver = {
.probe = pmic_probe,
...
}
==== pmic.c =============================================================
==== pmic-subdev.c ======================================================
static int sub_device_driver_probe(struct platform_device *sub_device)
{
... ...
}
static struct platform_driver pmic_subdev_driver = {
.name = sub-device-name,
.probe = sub_device_driver_probe,
... ...
}
==== pmic-subdev.c ======================================================
This, however, creates many questions you have to face with:
1. on what bus shall these sub-devices be?
** this is the reason I choose to use "platform_device", at least they
can reside on the platform_bus_type, thus platform_driver can be used
for this sub-device
2. these devices are actually useless except for linking the sub-device
to it's sub-device driver and of course, wasting memory. Normally in
the driver, another dedicated device will be created. E.g. let's take
a typical simple LED driver as an example:
static int __devinit xxxx_led_probe(struct BUS_device *dev)
{
struct led_classdev led;
/* initialize led */
...
led_classdev_register(dev, &led);
...
}
static struct BUS_driver xxxx_led_driver = {
.probe = xxxx_led_probe,
...
};
While led_classdev_register() is implemented something like:
int led_classdev_register(struct device *parent, struct led_classdev *led)
{
1. allocate led device
2. initialize led device
3. assign "struct class leds_class"
4. add device specific attributes
5. add device to a global list (LED subsystem specific)
return OK or ERR
}
As you notice, a new LED device is created. If there are multiple
LEDs on the chip, (yes, we have a PMIC with 5 LEDs on-chip, each
differs only at the offset of its controlling register), then you
probably will have to create 5 LED devices here, and manage them
in a whole. In recent terminology of the device-model, such device
is called a virtual device, it has no bus, no driver ..., it's used
so that class, attributes can attach.
Hence, here comes the 3rd and 4th questions:
3. Who should be the correct parent of these LED devices, the intermediate
sub-device we created just now? Or the pmic device on the I2C bus?
My answer is the the latter, obviously. However, writing code like:
led_classdev_register(pdev->dev.parent, &led_cdev)
is apparantly funny and misleading.
4. An intermediate device with no bus, no driver, no many other things
is really not something deserving a "struct device", that's a waste
of memory.
I'd really like to blame the device model that prevent me from writing a
perfect driver for such multi-function devices, otherwise I'd like to
blame the semiconductor manufacturers to come up with such devices making
us write ugly drivers. But I couldn't:
1. the PMIC design is nature, if I were the PMIC designer, with so many
analog circuitry already built-in, I would also add ADC, Touch, Audio
PWM, and many others to make them squeeze into the tiny chip. (No,
not including using I2C bus, which I'd prefer to avoid)
2. There's no fundamental flaw in the device model (sorry for the title),
of course, after weeks of reviewing the underlying code.
So probably, we are writing drivers in an incorrect way, that we have
ignored for the following reasons:
1. Most Linux developers are working on the PC world, which usually don't
have to care about the multi-function devices, one PCI card, one PCI
device, and one PCI device one function, we have a PCI driver to handle
that, in that PCI driver, implement the function we need, that's all.
2. Even if there are multiple sub-devices, they are probably of the same
functionality, and creating an array is easy, managing them in a whole
in the driver is really not a big deal.
3. We don't strictly distinguish between a physical device and a virtual
device, a physical device driver and a virtual device driver. E.g. a
virtual device could be a "net_device", "mtd_device", "led_classdev",
and many others
A normal device layout would be:
device specific
virtual bus type
|
platform_bus_type i2c_bus_type | virtual devices
| | | |
(device) V V V V
Platform BUS ---> I2C Controller ---> PMIC -+-> LED device (1)
|
+-> LED device (2)
|
+-> LED device (3)
|
+-> DC-DC Buck1
|
+-> DC-DC Buck2
|
+-> LDO1
|
+-> LDO2
|
+-> Backlight PWM1
|
+-> Backlight PWM2
|
...
the startup sequence would be:
1. bus controller being initialized
2. each bus_device on this bus controller is identified
3. registration of these bus_device[] triggers the match of the
bus_driver[] on this bus type
4. the bus_driver for this device creates all the virtual devices
found, (device can be registered to a virtual bus type)
5. virtual device driver matches the virtual device and implement
the function of this virtual device
Now it can be seen that:
1. virtual devices are no different than physical devices, instead
of being registered on a physical bus, they can be registered
on a device specific virtual bus, or a function specific bus.
(Let's say a net-bus, led-bus, mtd-bus, probably within
/sys/devices/virtual/net/, /sys/devices/virtual/mtd, and so on)
Or even on a global somewhat virtual_bus, which will probably
increase the iteration match time.
2. no intermediate device required for the device/driver match-up,
the parent of the virtual device is the physical device, and
this sounds reasonable
3. one virtual driver for many virtual devices of the same type,
driver doesn't have to managing them by array or link-list.
4. everything else still works
The only inconvenience would be for those physical device with only
_one_ virtual device, that by using a virtual device the driver, it
(we really want just a single driver for the physical device and the
virtual device) will possibly look like:
static int virtual_driver_probe ( struct device *virtual_device )
{
...
}
static virtual_device_driver {
.probe = virtual_driver_probe,
...
}
static int physical_driver_probe ( struct device *physical_device )
{
...
add_one_virtual_device(physical_device_as_parent);
...
return 0;
}
static physical_device_driver {
.probe = physical_driver_probe,
...
}
module_init:
physical_driver_register (&physical_device_driver);
virtual_driver_register (&virtual_device_driver);
This doesn't look nice, a generic virtual device driver is needed here.
If we define the network device as something like:
struct net_device {
struct device dev;
struct net_device_ops *ops;
...
}
static int generic_net_probe(struct device *dev)
{
struct net_device *net_dev = to_net_device(dev);
net_dev->ops->init();
add_to_net_device_list(net_dev);
register_to_network_layer(net_dev);
...
}
struct device_driver generic_net_device_driver = {
.name = "generic_virtual_net_device_driver",
.probe = generic_net_probe,
....
}
Then probably, each physical network device driver will be written like:
static int physical_driver_probe ( struct device *physical_device )
{
/* net_dev as the virtual device of this physical one */
struct net_device *net_dev;
...
net_dev->ops = xxxx;
add_one_virtual_device(physical_device_as_parent);
...
return 0;
}
static physical_device_driver {
.probe = physical_driver_probe,
...
}
module_init:
physical_driver_register (&physical_device_driver);
virtual_driver_register (&virtual_device_driver);
This is same as what we are doing now, the difference is that: instead of
calling register_netdev() by the physical driver itself, it's now creating
a virtual device and using the device-driver bus model for the generic
virtual network driver to match. As said, on such 1 physical device + 1
virtual device case, the benefit isn't significant. (that's why our driver
is written like it is today)
Finally comes an example of how LED device driver shall be implemented with
the above concept change, this is illustration _only_, no guarantee to work.
==== board.c ==============================================================
... ...
static struct gpio_led_platform_data board_leds[] = {
[0] = {
.name = "gpio-led",
.gpio = 10,
.active_low = 1,
},
[1] = {
.name = "gpio-led",
.gpio = 11,
.active_low = 1,
},
};
static void __init board_init_leds(void)
{
gpio_led_add(gpio_dev, 0, &board_debug_leds[0]);
gpio_led_add(gpio_dev, 1, &board_debug_leds[1]);
}
... ...
==== board.c ==============================================================
==== include/linux/leds/led.h =============================================
#ifndef __LED_H
#define __LED_H
#include <linux/device.h>
enum {
LED_OFF = 0,
LED_HALF = 127,
LED_FULL = 255,
};
struct led_device;
struct led_device_ops {
void (*set_brightness)(struct led_device *, int brightness);
int (*get_brightness)(struct led_device *);
};
struct led_device {
struct device dev;
const char *name;
int id;
int brightness;
struct led_device_ops *ops;
};
#define to_led_device(dev) container_of((dev), struct led_device, dev)
extern struct bus_type led_bus_type;
extern struct class led_class;
extern struct led_device *led_device_alloc(const char *name, int id);
extern int led_device_add(struct led_device *ldev);
extern int led_device_register(struct led_device *ldev);
extern void led_device_unregister(struct led_device *ldev);
#endif /* __LED_H */
==== include/linux/leds/led.h =============================================
==== drivers/leds/core.c ==================================================
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/leds/led.h>
static struct device led_bus_device = {
.bus_id = "leds",
};
static void led_device_release(struct device *dev)
{
kfree(to_led_device(dev));
}
struct led_device *led_device_alloc(const char *name, int id)
{
struct led_device *ldev;
ldev = kzalloc(sizeof(struct led_device), GFP_KERNEL);
if (ldev == NULL)
return NULL;
ldev->name = name;
ldev->id = id;
ldev->brightness = 0;
device_initialize(&ldev->dev);
ldev->dev.release = led_device_release;
return ldev;
}
EXPORT_SYMBOL_GPL(led_device_alloc);
int led_device_add(struct led_device *ldev)
{
if (!ldev)
return -EINVAL;
if (!ldev->dev.parent)
ldev->dev.parent = &led_bus_device;
ldev->dev.bus = &led_bus_type;
ldev->dev.class = &led_class;
if (ldev->id != -1)
snprintf(ldev->dev.bus_id, BUS_ID_SIZE, "%s.%d",
ldev->name, ldev->id);
else
strlcpy(ldev->dev.bus_id, ldev->name, BUS_ID_SIZE);
pr_debug("Registering led device '%s'. Parent at %s\n",
ldev->dev.bus_id, ldev->dev.parent->bus_id);
return device_add(&ldev->dev);
}
EXPORT_SYMBOL_GPL(led_device_add);
int led_device_register(struct led_device *ldev)
{
device_initialize(&ldev->dev);
return led_device_add(ldev);
}
EXPORT_SYMBOL_GPL(led_device_register);
void led_device_unregister(struct led_device *ldev)
{
if (ldev) {
device_del(&ldev->dev);
put_device(&ldev->dev);
}
}
EXPORT_SYMBOL_GPL(led_device_unregister);
static void led_set_brightness(struct led_device *ldev, int brightness)
{
if (ldev->ops && ldev->ops->set_brightness)
ldev->ops->set_brightness(ldev, brightness);
ldev->brightness = brightness;
}
static int led_get_brightness(struct led_device *ldev)
{
if (ldev->ops && ldev->ops->get_brightness)
return ldev->ops->get_brightness(ldev);
return ldev->brightness;
}
static ssize_t led_brightness_show(struct device *dev,
struct device_attribute *attr, char *buf)
{
return sprintf(buf, "%d\n", led_get_brightness(to_led_device(dev)));
}
static ssize_t led_brightness_store(struct device *dev,
struct device_attribute *attr, const char *buf, size_t count)
{
struct led_device *ldev = to_led_device(dev);
int brightness = (int) simple_strtol(buf, NULL, 0);
led_set_brightness(ldev, brightness);
return count;
}
static struct device_attribute led_device_attrs[] = {
__ATTR(brightness, 0644, led_brightness_show, led_brightness_store),
__ATTR_NULL,
};
#ifdef CONFIG_PM
static int led_class_suspend(struct device *dev, pm_message_t state)
{
struct led_device *ldev = to_led_device(dev);
if (ldev->ops && ldev->ops->set_brightness)
ldev->ops->set_brightness(ldev, 0);
return 0;
}
static int led_class_resume(struct device *dev)
{
struct led_device *ldev = to_led_device(dev);
if (ldev->ops && ldev->ops->set_brightness)
ldev->ops->set_brightness(ldev, ldev->brightness);
return 0;
}
#else
#define led_class_suspend NULL
#define led_class_resume NULL
#endif /* CONFIG_PM */
struct class led_class = {
.name = "led-class",
.owner = THIS_MODULE,
.dev_attrs = led_device_attrs,
.suspend = led_class_suspend,
.resume = led_class_resume,
};
EXPORT_SYMBOL_GPL(led_class);
static int led_bus_match(struct device *dev, struct device_driver *drv)
{
struct led_device *ldev = to_led_device(dev);
return (strncmp(ldev->name, drv->name, BUS_ID_SIZE) == 0);
}
static int led_bus_uevent(struct device *dev, struct kobj_uevent_env *env)
{
struct led_device *ldev = to_led_device(dev);
add_uevent_var(env, "MODALIAS=led:%s", ldev->name);
return 0;
}
struct bus_type led_bus_type = {
.name = "led-bus",
.match = led_bus_match,
.uevent = led_bus_uevent,
};
EXPORT_SYMBOL_GPL(led_bus_type);
static int __init led_core_init(void)
{
int err;
err = device_register(&led_bus_device);
if (err)
return err;
err = bus_register(&led_bus_type);
if (err)
return err;
err = class_register(&led_class);
if (err)
return err;
return 0;
}
subsys_initcall(led_core_init);
static void __exit led_core_exit(void)
{
class_unregister(&led_class);
bus_unregister(&led_bus_type);
device_unregister(&led_bus_device);
}
module_exit(led_core_exit);
==== drivers/leds/core.c ==================================================
==== include/linux/leds/led-gpio.h ========================================
#ifndef __LED_GPIO_H
#define __LED_GPIO_H
#include <linux/leds/led.h>
struct gpio_led_platform_data {
unsigned gpio;
unsigned active_low : 1;
};
extern struct led_device *gpio_led_add(struct device *parent, int id,
struct gpio_led_platform_data *pdata);
extern void gpio_led_del(struct led_device *ldev);
#endif /* __LED_GPIO_H */
==== include/linux/leds/led-gpio.h ========================================
==== drivers/leds/led-gpio.c ==============================================
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/gpio.h>
#include <linux/leds/led.h>
#include <linux/leds/led-gpio.h>
struct led_device *gpio_led_add(struct device *parent, int id,
struct gpio_led_platform_data *pdata)
{
struct led_device *ldev;
struct gpio_led_platform_data *p;
int err;
ldev = led_device_alloc("led-gpio", id);
if (ldev == NULL)
return NULL;
p = kmalloc(sizeof(struct gpio_led_platform_data), GFP_KERNEL);
if (p == NULL) {
kfree(ldev);
return NULL;
}
memcpy(p, pdata, sizeof(*pdata));
ldev->dev.platform_data = p;
err = led_device_add(ldev);
if (err) {
kfree(p);
put_device(&ldev->dev);
return NULL;
}
return ldev;
}
EXPORT_SYMBOL_GPL(gpio_led_add);
void gpio_led_del(struct led_device *ldev)
{
led_device_unregister(ldev);
}
EXPORT_SYMBOL_GPL(gpio_led_del);
struct gpio_led_data {
unsigned gpio;
unsigned active_low : 1;
unsigned can_sleep : 1;
int new_level;
struct work_struct work;
};
static void gpio_led_work(struct work_struct *work)
{
struct gpio_led_data *data;
data = container_of(work, struct gpio_led_data, work);
gpio_set_value_cansleep(data->gpio, data->new_level);
}
static void gpio_led_set_brightness(struct led_device *ldev, int brightness)
{
struct gpio_led_data *data = dev_get_drvdata(&ldev->dev);
int level;
level = (brightness == LED_OFF) ? 0 : 1;
if (data->active_low)
level = !level;
if (data->can_sleep) {
data->new_level = level;
schedule_work(&data->work);
} else
gpio_set_value(data->gpio, level);
}
static struct led_device_ops gpio_led_ops = {
.set_brightness = gpio_led_set_brightness,
};
static int __devinit gpio_led_probe(struct device *dev)
{
struct gpio_led_platform_data *pdata = dev->platform_data;
struct led_device *ldev = to_led_device(dev);
struct gpio_led_data *data;
int err;
ldev->ops = &gpio_led_ops;
err = gpio_request(pdata->gpio, dev->bus_id);
if (err) {
dev_err(dev, "failed to request GPIO%d\n", pdata->gpio);
return -EBUSY;
}
data = kzalloc(sizeof(struct gpio_led_data), GFP_KERNEL);
if (data == NULL) {
dev_err(dev, "failed to allocate memory\n");
return -ENOMEM;
}
data->gpio = pdata->gpio;
data->active_low = pdata->active_low;
data->can_sleep = gpio_cansleep(data->gpio);
data->new_level = 0;
INIT_WORK(&data->work, gpio_led_work);
dev_set_drvdata(dev, data);
return 0;
}
static int __devexit gpio_led_remove(struct device *dev)
{
struct gpio_led_data *data = dev_get_drvdata(dev);
kfree(data);
return 0;
}
static struct device_driver gpio_led_driver = {
.name = "led",
.bus = &led_bus_type,
.owner = THIS_MODULE,
.probe = gpio_led_probe,
.remove = __devexit_p(gpio_led_remove),
};
static int __init gpio_led_init(void)
{
return driver_register(&gpio_led_driver);
}
module_init(gpio_led_init);
static void __exit gpio_led_exit(void)
{
driver_unregister(&gpio_led_driver);
}
module_exit(gpio_led_exit);
==== drivers/leds/led-gpio.c ==============================================
Cheers
- eric
^ permalink raw reply [flat|nested] 10+ messages in thread
* Re: Fundamental Design Flaw of the Device Driver Model?
2008-08-22 6:25 Fundamental Design Flaw of the Device Driver Model? Eric Miao
@ 2008-08-22 9:03 ` Pavel Machek
2008-08-22 9:32 ` Eric Miao
2008-08-22 11:17 ` Liam Girdwood
` (2 subsequent siblings)
3 siblings, 1 reply; 10+ messages in thread
From: Pavel Machek @ 2008-08-22 9:03 UTC (permalink / raw)
To: Eric Miao; +Cc: LKML
> Fundamental Design Flaw of the Device Driver Model?
> ===================================================
>
> Sorry for the misleading subject, its purpose is to draw your attention :-)
> The ideas below are preliminary and I hope I'm not making serious mistakes
> here.
>
> This question has actually been around in my mind for several months, when
> I started to work on some devices with multiple functions. Specifically, a
> Power Management IC (PMIC in short in the following text) usually includes
> LEDs support (charging, indication...) audio, touch screen, power monitoring,
> LDOs, DC-DC bucks, and possibly some others.
>
> The initial two ideas came into my mind were:
>
> 1. separate the functions into multiple devices, write a driver for each
> of these devices
Go for 1.
> 4. An intermediate device with no bus, no driver, no many other things
> is really not something deserving a "struct device", that's a waste
> of memory.
Memory is not _that_ expensive, and struct device is not that
big. Adding infrastructure to driver model for supporting this would
also cost you memory, this time in .text segment.
Pavel
--
(english) http://www.livejournal.com/~pavelmachek
(cesky, pictures) http://atrey.karlin.mff.cuni.cz/~pavel/picture/horses/blog.html
^ permalink raw reply [flat|nested] 10+ messages in thread
* Re: Fundamental Design Flaw of the Device Driver Model?
2008-08-22 9:03 ` Pavel Machek
@ 2008-08-22 9:32 ` Eric Miao
2008-08-22 11:29 ` pHilipp Zabel
0 siblings, 1 reply; 10+ messages in thread
From: Eric Miao @ 2008-08-22 9:32 UTC (permalink / raw)
To: Pavel Machek; +Cc: LKML
[-- Attachment #1: Type: text/plain, Size: 2134 bytes --]
On Fri, Aug 22, 2008 at 5:03 PM, Pavel Machek <pavel@suse.cz> wrote:
>> Fundamental Design Flaw of the Device Driver Model?
>> ===================================================
>>
>> Sorry for the misleading subject, its purpose is to draw your attention :-)
>> The ideas below are preliminary and I hope I'm not making serious mistakes
>> here.
>>
>> This question has actually been around in my mind for several months, when
>> I started to work on some devices with multiple functions. Specifically, a
>> Power Management IC (PMIC in short in the following text) usually includes
>> LEDs support (charging, indication...) audio, touch screen, power monitoring,
>> LDOs, DC-DC bucks, and possibly some others.
>>
>> The initial two ideas came into my mind were:
>>
>> 1. separate the functions into multiple devices, write a driver for each
>> of these devices
>
> Go for 1.
>
>> 4. An intermediate device with no bus, no driver, no many other things
>> is really not something deserving a "struct device", that's a waste
>> of memory.
>
> Memory is not _that_ expensive, and struct device is not that
> big. Adding infrastructure to driver model for supporting this would
> also cost you memory, this time in .text segment.
>
Actually I don't mind wasting additional memory for a better structure,
but option (1) isn't. And I just assume you have read to the end of thi
mail, then you will see it's more like a concept change,no fundamental
change to the code itself.
Actually the reason to have two different kind of devices are obvious:
1. physical device - handles the bus related operations (read, write,
locking, I/O)
2. virtual device - handles the functionality (interface with the
upper framework)
With this separation, the same IP on different semiconductor can be
shared naturally. There are lot of such products esp in embedded market,
with multiple shared IPs within a single die.
Attached is a patch series to get you guys a feeling how the option
(1) will look like, and think again about the device-driver relationship,
the correctness of introducing an intermediate platform_device.
[-- Warning: decoded text below may be mangled, UTF-8 assumed --]
[-- Attachment #2: 0001-i2c-add-base-support-for-DA9030-DA9034.patch --]
[-- Type: text/x-diff; name=0001-i2c-add-base-support-for-DA9030-DA9034.patch, Size: 21590 bytes --]
From 496782b57a16df12dd31cd9abd006438ab7db75c Mon Sep 17 00:00:00 2001
From: Eric Miao <eric.miao@marvell.com>
Date: Thu, 14 Aug 2008 16:27:07 +0800
Subject: [PATCH] i2c: add base support for DA9030/DA9034
Signed-off-by: Mike Rapoport <mike@compulab.il.co>
Signed-off-by: Eric Miao <eric.miao@marvell.com>
---
drivers/i2c/chips/Kconfig | 11 +
drivers/i2c/chips/Makefile | 1 +
drivers/i2c/chips/da903x.c | 563 ++++++++++++++++++++++++++++++++++++++++++++
include/linux/i2c/da903x.h | 165 +++++++++++++
4 files changed, 740 insertions(+), 0 deletions(-)
create mode 100644 drivers/i2c/chips/da903x.c
create mode 100644 include/linux/i2c/da903x.h
diff --git a/drivers/i2c/chips/Kconfig b/drivers/i2c/chips/Kconfig
index a95cb94..5b51e84 100644
--- a/drivers/i2c/chips/Kconfig
+++ b/drivers/i2c/chips/Kconfig
@@ -172,4 +172,15 @@ config MENELAUS
and other features that are often used in portable devices like
cell phones and PDAs.
+config PMIC_DA903X
+ bool "Dialog Semiconductor DA9030/DA9034 PMIC Support"
+ depends on I2C=y && ARCH_PXA
+ help
+ Say yes here to support for Dialog Semiconductor DA9030 (a.k.a
+ ARAVA) and DA9034 (a.k.a MICCO), these are Power Management IC
+ usually found on PXA processors-based platforms. This includes
+ the I2C driver and the core APIs _only_, you have to select
+ individual components like LCD backlight, voltage regulators,
+ LEDs and battery-charger under the corresponding menus.
+
endmenu
diff --git a/drivers/i2c/chips/Makefile b/drivers/i2c/chips/Makefile
index 39e3e69..c5323aa 100644
--- a/drivers/i2c/chips/Makefile
+++ b/drivers/i2c/chips/Makefile
@@ -21,6 +21,7 @@ obj-$(CONFIG_ISP1301_OMAP) += isp1301_omap.o
obj-$(CONFIG_TPS65010) += tps65010.o
obj-$(CONFIG_MENELAUS) += menelaus.o
obj-$(CONFIG_SENSORS_TSL2550) += tsl2550.o
+obj-$(CONFIG_PMIC_DA903X) += da903x.o
ifeq ($(CONFIG_I2C_DEBUG_CHIP),y)
EXTRA_CFLAGS += -DDEBUG
diff --git a/drivers/i2c/chips/da903x.c b/drivers/i2c/chips/da903x.c
new file mode 100644
index 0000000..2eb559d
--- /dev/null
+++ b/drivers/i2c/chips/da903x.c
@@ -0,0 +1,563 @@
+/*
+ * Base driver for Dialog Semiconductor DA9030/DA9034
+ *
+ * Copyright (C) 2008 Compulab, Ltd.
+ * Mike Rapoport <mike@compulab.co.il>
+ *
+ * Copyright (C) 2006-2008 Marvell International Ltd.
+ * Eric Miao <eric.miao@marvell.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/module.h>
+#include <linux/interrupt.h>
+#include <linux/platform_device.h>
+#include <linux/i2c.h>
+#include <linux/i2c/da903x.h>
+
+#define DA9030_CHIP_ID 0x00
+#define DA9030_EVENT_A 0x01
+#define DA9030_EVENT_B 0x02
+#define DA9030_EVENT_C 0x03
+#define DA9030_STATUS 0x04
+#define DA9030_IRQ_MASK_A 0x05
+#define DA9030_IRQ_MASK_B 0x06
+#define DA9030_IRQ_MASK_C 0x07
+#define DA9030_SYS_CTRL_A 0x08
+#define DA9030_SYS_CTRL_B 0x09
+#define DA9030_FAULT_LOG 0x0a
+
+#define DA9034_CHIP_ID 0x00
+#define DA9034_EVENT_A 0x01
+#define DA9034_EVENT_B 0x02
+#define DA9034_EVENT_C 0x03
+#define DA9034_EVENT_D 0x04
+#define DA9034_STATUS_A 0x05
+#define DA9034_STATUS_B 0x06
+#define DA9034_IRQ_MASK_A 0x07
+#define DA9034_IRQ_MASK_B 0x08
+#define DA9034_IRQ_MASK_C 0x09
+#define DA9034_IRQ_MASK_D 0x0a
+#define DA9034_SYS_CTRL_A 0x0b
+#define DA9034_SYS_CTRL_B 0x0c
+#define DA9034_FAULT_LOG 0x0d
+
+struct da903x_chip;
+
+struct da903x_chip_ops {
+ int (*init_chip)(struct da903x_chip *);
+ int (*unmask_events)(struct da903x_chip *, unsigned int events);
+ int (*mask_events)(struct da903x_chip *, unsigned int events);
+ int (*read_events)(struct da903x_chip *, unsigned int *events);
+ int (*read_status)(struct da903x_chip *, unsigned int *status);
+};
+
+struct da903x_chip {
+ struct i2c_client *client;
+ struct device *dev;
+ struct da903x_chip_ops *ops;
+
+ int type;
+ uint32_t events_mask;
+
+ struct mutex lock;
+ struct work_struct irq_work;
+
+ struct blocking_notifier_head notifier_list;
+};
+
+static int __da903x_read(struct i2c_client *client, int reg, uint8_t *val)
+{
+ int ret;
+
+ ret = i2c_smbus_read_byte_data(client, reg);
+ if (ret < 0) {
+ dev_err(&client->dev, "failed reading at 0x%02x\n", reg);
+ return ret;
+ }
+
+ *val = (uint8_t)ret;
+ return 0;
+}
+
+static int __da903x_reads(struct i2c_client *client, int reg,
+ int len, uint8_t *val)
+{
+ int ret;
+
+ ret = i2c_smbus_read_i2c_block_data(client, reg, len, val);
+ if (ret < 0) {
+ dev_err(&client->dev, "failed reading from 0x%02x\n", reg);
+ return ret;
+ }
+ return 0;
+}
+
+static int __da903x_write(struct i2c_client *client, int reg, uint8_t val)
+{
+ int ret;
+
+ ret = i2c_smbus_write_byte_data(client, reg, val);
+ if (ret < 0) {
+ dev_err(&client->dev, "failed writing 0x%02x to 0x%02x\n",
+ val, reg);
+ return ret;
+ }
+ return 0;
+}
+
+static int __da903x_writes(struct i2c_client *client, int reg,
+ int len, uint8_t *val)
+{
+ int ret;
+
+ ret = i2c_smbus_write_i2c_block_data(client, reg, len, val);
+ if (ret < 0) {
+ dev_err(&client->dev, "failed writings to 0x%02x\n", reg);
+ return ret;
+ }
+ return 0;
+}
+
+int da903x_register_notifier(struct device *dev, struct notifier_block *nb,
+ unsigned int events)
+{
+ struct da903x_chip *chip = dev_get_drvdata(dev);
+
+ chip->ops->unmask_events(chip, events);
+ return blocking_notifier_chain_register(&chip->notifier_list, nb);
+}
+EXPORT_SYMBOL_GPL(da903x_register_notifier);
+
+int da903x_unregister_notifier(struct device *dev, struct notifier_block *nb,
+ unsigned int events)
+{
+ struct da903x_chip *chip = dev_get_drvdata(dev);
+
+ chip->ops->mask_events(chip, events);
+ return blocking_notifier_chain_unregister(&chip->notifier_list, nb);
+}
+EXPORT_SYMBOL_GPL(da903x_unregister_notifier);
+
+int da903x_write(struct device *dev, int reg, uint8_t val)
+{
+ return __da903x_write(to_i2c_client(dev), reg, val);
+}
+EXPORT_SYMBOL_GPL(da903x_write);
+
+int da903x_read(struct device *dev, int reg, uint8_t *val)
+{
+ return __da903x_read(to_i2c_client(dev), reg, val);
+}
+EXPORT_SYMBOL_GPL(da903x_read);
+
+int da903x_set_bit(struct device *dev, int reg, uint8_t bit_mask)
+{
+ struct da903x_chip *chip = dev_get_drvdata(dev);
+ uint8_t reg_val;
+ int ret = 0;
+
+ mutex_lock(&chip->lock);
+
+ ret = __da903x_read(chip->client, reg, ®_val);
+ if (ret)
+ goto out;
+
+ if ((reg_val & bit_mask) == 0) {
+ reg_val |= bit_mask;
+ ret = __da903x_write(chip->client, reg, reg_val);
+ }
+out:
+ mutex_unlock(&chip->lock);
+ return ret;
+}
+EXPORT_SYMBOL_GPL(da903x_set_bit);
+
+int da903x_clr_bit(struct device *dev, int reg, uint8_t bit_mask)
+{
+ struct da903x_chip *chip = dev_get_drvdata(dev);
+ uint8_t reg_val;
+ int ret = 0;
+
+ mutex_lock(&chip->lock);
+
+ ret = __da903x_read(chip->client, reg, ®_val);
+ if (ret)
+ goto out;
+
+ if (reg_val & bit_mask) {
+ reg_val &= ~bit_mask;
+ ret = __da903x_write(chip->client, reg, reg_val);
+ }
+out:
+ mutex_unlock(&chip->lock);
+ return ret;
+}
+EXPORT_SYMBOL_GPL(da903x_clr_bit);
+
+int da903x_update(struct device *dev, int reg, uint8_t val, uint8_t mask)
+{
+ struct da903x_chip *chip = dev_get_drvdata(dev);
+ uint8_t reg_val;
+ int ret = 0;
+
+ mutex_lock(&chip->lock);
+
+ ret = __da903x_read(chip->client, reg, ®_val);
+ if (ret)
+ goto out;
+
+ if ((reg_val & mask) != val) {
+ reg_val = (reg_val & ~mask) | val;
+ ret = __da903x_write(chip->client, reg, reg_val);
+ }
+out:
+ mutex_unlock(&chip->lock);
+ return ret;
+}
+EXPORT_SYMBOL_GPL(da903x_update);
+
+int da903x_query_status(struct device *dev, unsigned int sbits)
+{
+ struct da903x_chip *chip = dev_get_drvdata(dev);
+ unsigned int status = 0;
+
+ chip->ops->read_status(chip, &status);
+ return ((status & sbits) == sbits);
+}
+EXPORT_SYMBOL(da903x_query_status);
+
+static int __devinit da9030_init_chip(struct da903x_chip *chip)
+{
+ uint8_t chip_id;
+ int err;
+
+ err = __da903x_read(chip->client, DA9030_CHIP_ID, &chip_id);
+ if (err)
+ return err;
+
+ err = __da903x_write(chip->client, DA9030_SYS_CTRL_A, 0xE8);
+ if (err)
+ return err;
+
+ dev_info(chip->dev, "DA9030 (CHIP ID: 0x%02x) detected\n", chip_id);
+ return 0;
+}
+
+static int da9030_unmask_events(struct da903x_chip *chip, unsigned int events)
+{
+ uint8_t v[3];
+
+ chip->events_mask &= ~events;
+
+ v[0] = (chip->events_mask & 0xff);
+ v[1] = (chip->events_mask >> 8) & 0xff;
+ v[2] = (chip->events_mask >> 16) & 0xff;
+
+ return __da903x_writes(chip->client, DA9030_IRQ_MASK_A, 3, v);
+}
+
+static int da9030_mask_events(struct da903x_chip *chip, unsigned int events)
+{
+ uint8_t v[3];
+
+ chip->events_mask &= ~events;
+
+ v[0] = (chip->events_mask & 0xff);
+ v[1] = (chip->events_mask >> 8) & 0xff;
+ v[2] = (chip->events_mask >> 16) & 0xff;
+
+ return __da903x_writes(chip->client, DA9030_IRQ_MASK_A, 3, v);
+}
+
+static int da9030_read_events(struct da903x_chip *chip, unsigned int *events)
+{
+ uint8_t v[3] = {0, 0, 0};
+ int ret;
+
+ ret = __da903x_reads(chip->client, DA9030_EVENT_A, 3, v);
+ if (ret < 0)
+ return ret;
+
+ *events = (v[2] << 16) | (v[1] << 8) | v[0];
+ return 0;
+}
+
+static int da9030_read_status(struct da903x_chip *chip, unsigned int *status)
+{
+ return __da903x_read(chip->client, DA9030_STATUS, (uint8_t *)status);
+}
+
+static int da9034_init_chip(struct da903x_chip *chip)
+{
+ uint8_t chip_id;
+ int err;
+
+ err = __da903x_read(chip->client, DA9034_CHIP_ID, &chip_id);
+ if (err)
+ return err;
+
+ err = __da903x_write(chip->client, DA9034_SYS_CTRL_A, 0xE8);
+ if (err)
+ return err;
+
+ /* avoid SRAM power off during sleep*/
+ __da903x_write(chip->client, 0x10, 0x07);
+ __da903x_write(chip->client, 0x11, 0xff);
+ __da903x_write(chip->client, 0x12, 0xff);
+
+ /* Enable the ONKEY power down functionality */
+ __da903x_write(chip->client, DA9034_SYS_CTRL_B, 0x20);
+ __da903x_write(chip->client, DA9034_SYS_CTRL_A, 0x60);
+
+ /* workaround to make LEDs work */
+ __da903x_write(chip->client, 0x90, 0x01);
+ __da903x_write(chip->client, 0xB0, 0x08);
+
+ dev_info(chip->dev, "DA9034 (CHIP ID: 0x%02x) detected\n", chip_id);
+ return 0;
+}
+
+static int da9034_unmask_events(struct da903x_chip *chip, unsigned int events)
+{
+ uint8_t v[4];
+
+ chip->events_mask &= ~events;
+
+ v[0] = (chip->events_mask & 0xff);
+ v[1] = (chip->events_mask >> 8) & 0xff;
+ v[2] = (chip->events_mask >> 16) & 0xff;
+ v[3] = (chip->events_mask >> 24) & 0xff;
+
+ return __da903x_writes(chip->client, DA9034_IRQ_MASK_A, 4, v);
+}
+
+static int da9034_mask_events(struct da903x_chip *chip, unsigned int events)
+{
+ uint8_t v[4];
+
+ chip->events_mask |= events;
+
+ v[0] = (chip->events_mask & 0xff);
+ v[1] = (chip->events_mask >> 8) & 0xff;
+ v[2] = (chip->events_mask >> 16) & 0xff;
+ v[3] = (chip->events_mask >> 24) & 0xff;
+
+ return __da903x_writes(chip->client, DA9034_IRQ_MASK_A, 4, v);
+}
+
+static int da9034_read_events(struct da903x_chip *chip, unsigned int *events)
+{
+ uint8_t v[4] = {0, 0, 0, 0};
+ int ret;
+
+ ret = __da903x_reads(chip->client, DA9034_EVENT_A, 4, v);
+ if (ret < 0)
+ return ret;
+
+ *events = (v[3] << 24) | (v[2] << 16) | (v[1] << 8) | v[0];
+ return 0;
+}
+
+static int da9034_read_status(struct da903x_chip *chip, unsigned int *status)
+{
+ uint8_t v[2] = {0, 0};
+ int ret = 0;
+
+ ret = __da903x_reads(chip->client, DA9034_STATUS_A, 2, v);
+ if (ret)
+ return ret;
+
+ *status = (v[1] << 8) | v[0];
+ return 0;
+}
+
+static void da903x_irq_work(struct work_struct *work)
+{
+ struct da903x_chip *chip =
+ container_of(work, struct da903x_chip, irq_work);
+ unsigned int events = 0;
+
+ while (1) {
+ if (chip->ops->read_events(chip, &events))
+ break;
+
+ events &= ~chip->events_mask;
+ if (events == 0)
+ break;
+
+ blocking_notifier_call_chain(
+ &chip->notifier_list, events, NULL);
+ }
+ enable_irq(chip->client->irq);
+}
+
+static int da903x_irq_handler(int irq, void *data)
+{
+ struct da903x_chip *chip = data;
+
+ disable_irq_nosync(irq);
+ (void)schedule_work(&chip->irq_work);
+
+ return IRQ_HANDLED;
+}
+
+static int da903x_remove_subdev(struct device *dev, void *data)
+{
+ struct platform_device *pdev = data;
+
+ platform_device_unregister(pdev);
+ return 0;
+}
+
+static void da903x_remove_subdevs(struct da903x_chip *chip)
+{
+ device_for_each_child(chip->dev, NULL, da903x_remove_subdev);
+}
+
+static int da903x_add_subdevs(struct da903x_chip *chip,
+ struct da903x_subdev_info *subdevs,
+ int num)
+{
+ struct platform_device *pdev;
+ int i, ret;
+
+ for (i = 0; i < num; i++) {
+ pdev = platform_device_alloc(subdevs[i].name, subdevs[i].id);
+ if (pdev == NULL) {
+ dev_err(chip->dev, "failed to alloc sub device(%s)\n",
+ subdevs[i].name);
+ ret = -ENOMEM;
+ goto err;
+ }
+
+ pdev->dev.parent = chip->dev;
+ pdev->dev.platform_data = subdevs[i].platform_data;
+
+ ret = platform_device_add(pdev);
+ if (ret) {
+ dev_err(chip->dev, "failed to add sub device(%s)\n",
+ subdevs[i].name);
+ goto err;
+ }
+ }
+ return 0;
+err:
+ da903x_remove_subdevs(chip);
+ return ret;
+}
+
+static int __devinit da903x_probe(struct i2c_client *client,
+ const struct i2c_device_id *id)
+{
+ struct da903x_platform_data *pdata = client->dev.platform_data;
+ struct da903x_chip *chip;
+ unsigned int tmp;
+ int ret;
+
+ chip = kzalloc(sizeof(struct da903x_chip), GFP_KERNEL);
+ if (chip == NULL)
+ return -ENOMEM;
+
+ chip->client = client;
+ chip->dev = &client->dev;
+ chip->ops = (struct da903x_chip_ops *)id->driver_data;
+
+ mutex_init(&chip->lock);
+ INIT_WORK(&chip->irq_work, da903x_irq_work);
+ BLOCKING_INIT_NOTIFIER_HEAD(&chip->notifier_list);
+
+ ret = chip->ops->init_chip(chip);
+ if (ret)
+ goto out_free_chip;
+
+ /* mask and clear all IRQs */
+ chip->events_mask = 0xffffffff;
+ chip->ops->mask_events(chip, chip->events_mask);
+ chip->ops->read_events(chip, &tmp);
+
+ ret = request_irq(client->irq, da903x_irq_handler,
+ IRQF_DISABLED | IRQF_TRIGGER_FALLING,
+ "da903x", chip);
+ if (ret) {
+ dev_err(&client->dev, "failed to request irq %d\n",
+ client->irq);
+ goto out_free_chip;
+ }
+
+ ret = da903x_add_subdevs(chip, pdata->subdevs, pdata->num_subdevs);
+ if (ret)
+ goto out_free_irq;
+
+ i2c_set_clientdata(client, chip);
+ return 0;
+
+out_free_irq:
+ free_irq(client->irq, chip);
+out_free_chip:
+ kfree(chip);
+ return ret;
+}
+
+static int __devexit da903x_remove(struct i2c_client *client)
+{
+ struct da903x_chip *chip = i2c_get_clientdata(client);
+
+ da903x_remove_subdevs(chip);
+ kfree(chip);
+ return 0;
+}
+
+static struct da903x_chip_ops da9030_ops = {
+ .init_chip = da9030_init_chip,
+ .unmask_events = da9030_unmask_events,
+ .mask_events = da9030_mask_events,
+ .read_events = da9030_read_events,
+ .read_status = da9030_read_status,
+};
+
+static struct da903x_chip_ops da9034_ops = {
+ .init_chip = da9034_init_chip,
+ .unmask_events = da9034_unmask_events,
+ .mask_events = da9034_mask_events,
+ .read_events = da9034_read_events,
+ .read_status = da9034_read_status,
+};
+
+static const struct i2c_device_id da903x_id_table[] = {
+ { "da9030", (unsigned long)&da9030_ops, },
+ { "da9034", (unsigned long)&da9034_ops, },
+ { },
+};
+MODULE_DEVICE_TABLE(i2c, da903x_id_table);
+
+static struct i2c_driver da903x_driver = {
+ .driver = {
+ .name = "da903x",
+ .owner = THIS_MODULE,
+ },
+ .probe = da903x_probe,
+ .remove = __devexit_p(da903x_remove),
+ .id_table = da903x_id_table,
+};
+
+static int __init da903x_init(void)
+{
+ return i2c_add_driver(&da903x_driver);
+}
+module_init(da903x_init);
+
+static void __exit da903x_exit(void)
+{
+ i2c_del_driver(&da903x_driver);
+}
+module_exit(da903x_exit);
+
+MODULE_DESCRIPTION("PMIC Driver for Dialog Semiconductor DA9034");
+MODULE_AUTHOR("Eric Miao <eric.miao@marvell.com>"
+ "Mike Rapoport <mike@compulab.co.il>");
+MODULE_LICENSE("GPL");
diff --git a/include/linux/i2c/da903x.h b/include/linux/i2c/da903x.h
new file mode 100644
index 0000000..60434c7
--- /dev/null
+++ b/include/linux/i2c/da903x.h
@@ -0,0 +1,165 @@
+#ifndef __LINUX_PMIC_DA903X_H
+#define __LINUX_PMIC_DA903X_H
+
+#include <linux/leds.h>
+
+/* Unified sub device IDs for DA9030/DA9034 */
+enum {
+ DA9030_ID_LED_1,
+ DA9030_ID_LED_2,
+ DA9030_ID_LED_3,
+ DA9030_ID_LED_4,
+ DA9030_ID_VIBRA,
+ DA9030_ID_WLED,
+
+ DA9034_ID_LED_1,
+ DA9034_ID_LED_2,
+ DA9034_ID_VIBRA,
+ DA9034_ID_WLED,
+ DA9034_ID_TOUCH,
+};
+
+/*
+ * DA9030/DA9034 LEDs sub-devices uses generic "struct led_info"
+ * as the platform_data
+ */
+
+/* DA9030 flags for "struct led_info"
+ */
+#define DA9030_LED_RATE_ON (0 << 5)
+#define DA9030_LED_RATE_052S (1 << 5)
+#define DA9030_LED_DUTY_1_16 (0 << 3)
+#define DA9030_LED_DUTY_1_8 (1 << 3)
+#define DA9030_LED_DUTY_1_4 (2 << 3)
+#define DA9030_LED_DUTY_1_2 (3 << 3)
+
+#define DA9030_VIBRA_MODE_1P3V (0 << 1)
+#define DA9030_VIBRA_MODE_2P7V (1 << 1)
+#define DA9030_VIBRA_FREQ_1HZ (0 << 2)
+#define DA9030_VIBRA_FREQ_2HZ (1 << 2)
+#define DA9030_VIBRA_FREQ_4HZ (2 << 2)
+#define DA9030_VIBRA_FREQ_8HZ (3 << 2)
+#define DA9030_VIBRA_DUTY_ON (0 << 4)
+#define DA9030_VIBRA_DUTY_75P (1 << 4)
+#define DA9030_VIBRA_DUTY_50P (2 << 4)
+#define DA9030_VIBRA_DUTY_25P (3 << 4)
+
+/* DA9034 flags for "struct led_info" */
+#define DA9034_LED_RAMP (1 << 7)
+
+/* DA9034 touch screen platform data */
+struct da9034_touch_pdata {
+ int interval_ms; /* sampling interval while pen down */
+ int x_inverted;
+ int y_inverted;
+};
+
+struct da903x_subdev_info {
+ const char *name;
+ int id;
+ void *platform_data;
+};
+
+struct da903x_platform_data {
+ uint32_t events_enabled;
+ void (*event_handler)(struct device *dev, uint32_t events);
+
+ int num_subdevs;
+ struct da903x_subdev_info *subdevs;
+};
+
+/* bit definitions for DA9030 events */
+#define DA9030_EVENT_ONKEY (1 << 0)
+#define DA9030_EVENT_PWREN (1 << 1)
+#define DA9030_EVENT_EXTON (1 << 2)
+#define DA9030_EVENT_CHDET (1 << 3)
+#define DA9030_EVENT_TBAT (1 << 4)
+#define DA9030_EVENT_VBATMON (1 << 5)
+#define DA9030_EVENT_VBATMON_TXON (1 << 6)
+#define DA9030_EVENT_CHIOVER (1 << 7)
+#define DA9030_EVENT_TCTO (1 << 8)
+#define DA9030_EVENT_CCTO (1 << 9)
+#define DA9030_EVENT_ADC_READY (1 << 10)
+#define DA9030_EVENT_VBUS_4P4 (1 << 11)
+#define DA9030_EVENT_VBUS_4P0 (1 << 12)
+#define DA9030_EVENT_SESS_VALID (1 << 13)
+#define DA9030_EVENT_SRP_DETECT (1 << 14)
+#define DA9030_EVENT_WATCHDOG (1 << 15)
+#define DA9030_EVENT_LDO15 (1 << 16)
+#define DA9030_EVENT_LDO16 (1 << 17)
+#define DA9030_EVENT_LDO17 (1 << 18)
+#define DA9030_EVENT_LDO18 (1 << 19)
+#define DA9030_EVENT_LDO19 (1 << 20)
+#define DA9030_EVENT_BUCK2 (1 << 21)
+
+/* bit definitions for DA9034 events */
+#define DA9034_EVENT_ONKEY (1 << 0)
+#define DA9034_EVENT_EXTON (1 << 2)
+#define DA9034_EVENT_CHDET (1 << 3)
+#define DA9034_EVENT_TBAT (1 << 4)
+#define DA9034_EVENT_VBATMON (1 << 5)
+#define DA9034_EVENT_REV_IOVER (1 << 6)
+#define DA9034_EVENT_CH_IOVER (1 << 7)
+#define DA9034_EVENT_CH_TCTO (1 << 8)
+#define DA9034_EVENT_CH_CCTO (1 << 9)
+#define DA9034_EVENT_USB_DEV (1 << 10)
+#define DA9034_EVENT_OTGCP_IOVER (1 << 11)
+#define DA9034_EVENT_VBUS_4P55 (1 << 12)
+#define DA9034_EVENT_VBUS_3P8 (1 << 13)
+#define DA9034_EVENT_SESS_1P8 (1 << 14)
+#define DA9034_EVENT_SRP_READY (1 << 15)
+#define DA9034_EVENT_ADC_MAN (1 << 16)
+#define DA9034_EVENT_ADC_AUTO4 (1 << 17)
+#define DA9034_EVENT_ADC_AUTO5 (1 << 18)
+#define DA9034_EVENT_ADC_AUTO6 (1 << 19)
+#define DA9034_EVENT_PEN_DOWN (1 << 20)
+#define DA9034_EVENT_TSI_READY (1 << 21)
+#define DA9034_EVENT_UART_TX (1 << 22)
+#define DA9034_EVENT_UART_RX (1 << 23)
+#define DA9034_EVENT_HEADSET (1 << 25)
+#define DA9034_EVENT_HOOKSWITCH (1 << 26)
+#define DA9034_EVENT_WATCHDOG (1 << 27)
+
+extern int da903x_register_notifier(struct device *dev,
+ struct notifier_block *nb, unsigned int events);
+extern int da903x_unregister_notifier(struct device *dev,
+ struct notifier_block *nb, unsigned int events);
+
+/* Status Query Interface */
+#define DA9030_STATUS_ONKEY (1 << 0)
+#define DA9030_STATUS_PWREN1 (1 << 1)
+#define DA9030_STATUS_EXTON (1 << 2)
+#define DA9030_STATUS_CHDET (1 << 3)
+#define DA9030_STATUS_TBAT (1 << 4)
+#define DA9030_STATUS_VBATMON (1 << 5)
+#define DA9030_STATUS_VBATMON_TXON (1 << 6)
+#define DA9030_STATUS_MCLKDET (1 << 7)
+
+#define DA9034_STATUS_ONKEY (1 << 0)
+#define DA9034_STATUS_EXTON (1 << 2)
+#define DA9034_STATUS_CHDET (1 << 3)
+#define DA9034_STATUS_TBAT (1 << 4)
+#define DA9034_STATUS_VBATMON (1 << 5)
+#define DA9034_STATUS_PEN_DOWN (1 << 6)
+#define DA9034_STATUS_MCLKDET (1 << 7)
+#define DA9034_STATUS_USB_DEV (1 << 8)
+#define DA9034_STATUS_HEADSET (1 << 9)
+#define DA9034_STATUS_HOOKSWITCH (1 << 10)
+#define DA9034_STATUS_REMCON (1 << 11)
+#define DA9034_STATUS_VBUS_VALID_4P55 (1 << 12)
+#define DA9034_STATUS_VBUS_VALID_3P8 (1 << 13)
+#define DA9034_STATUS_SESS_VALID_1P8 (1 << 14)
+#define DA9034_STATUS_SRP_READY (1 << 15)
+
+extern int da903x_query_status(struct device *dev, unsigned int status);
+
+
+/* NOTE: the two functions below are not intended for use outside
+ * of the DA9034 sub-device drivers
+ */
+extern int da903x_write(struct device *dev, int reg, uint8_t val);
+extern int da903x_read(struct device *dev, int reg, uint8_t *val);
+extern int da903x_update(struct device *dev, int reg, uint8_t val, uint8_t mask);
+extern int da903x_set_bit(struct device *dev, int reg, uint8_t bit_mask);
+extern int da903x_clr_bit(struct device *dev, int reg, uint8_t bit_mask);
+#endif /* __LINUX_PMIC_DA903X_H */
--
1.5.4.3
[-- Warning: decoded text below may be mangled, UTF-8 assumed --]
[-- Attachment #3: 0002-leds-add-support-of-on-chip-LED-drivers-for-DA9030.patch --]
[-- Type: text/x-diff; name=0002-leds-add-support-of-on-chip-LED-drivers-for-DA9030.patch, Size: 6415 bytes --]
From b7a928304e90f08f29d08656e98850aa7a721a86 Mon Sep 17 00:00:00 2001
From: Eric Miao <eric.miao@marvell.com>
Date: Thu, 14 Aug 2008 16:32:41 +0800
Subject: [PATCH] leds: add support of on-chip LED drivers for DA9030/DA9034
Signed-off-by: Mike Rapoport <mike@compulab.il.co>
Signed-off-by: Eric Miao <eric.miao@marvell.com>
---
drivers/leds/Kconfig | 7 ++
drivers/leds/Makefile | 1 +
drivers/leds/leds-da903x.c | 172 ++++++++++++++++++++++++++++++++++++++++++++
3 files changed, 180 insertions(+), 0 deletions(-)
create mode 100644 drivers/leds/leds-da903x.c
diff --git a/drivers/leds/Kconfig b/drivers/leds/Kconfig
index 9556262..dd2cef2 100644
--- a/drivers/leds/Kconfig
+++ b/drivers/leds/Kconfig
@@ -163,6 +163,13 @@ config LEDS_PCA955X
LED driver chips accessed via the I2C bus. Supported
devices include PCA9550, PCA9551, PCA9552, and PCA9553.
+config LEDS_DA903X
+ tristate "LED Support for DA9030/DA9034 PMIC"
+ depends on LEDS_CLASS && PMIC_DA903X
+ help
+ This option enables support for on-chip LED drivers found
+ on Dialog Semiconductor DA9030/DA9034 PMICs.
+
comment "LED Triggers"
config LEDS_TRIGGERS
diff --git a/drivers/leds/Makefile b/drivers/leds/Makefile
index ff7982b..88cb26d 100644
--- a/drivers/leds/Makefile
+++ b/drivers/leds/Makefile
@@ -23,6 +23,7 @@ obj-$(CONFIG_LEDS_CLEVO_MAIL) += leds-clevo-mail.o
obj-$(CONFIG_LEDS_HP6XX) += leds-hp6xx.o
obj-$(CONFIG_LEDS_FSG) += leds-fsg.o
obj-$(CONFIG_LEDS_PCA955X) += leds-pca955x.o
+obj-$(CONFIG_LEDS_DA903X) += leds-da903x.o
# LED Triggers
obj-$(CONFIG_LEDS_TRIGGER_TIMER) += ledtrig-timer.o
diff --git a/drivers/leds/leds-da903x.c b/drivers/leds/leds-da903x.c
new file mode 100644
index 0000000..71095e3
--- /dev/null
+++ b/drivers/leds/leds-da903x.c
@@ -0,0 +1,172 @@
+/*
+ * LEDs driver for Dialog Semiconductor DA9030/DA9034
+ *
+ * Copyright (C) 2008 Compulab, Ltd.
+ * Mike Rapoport <mike@compulab.co.il>
+ *
+ * Copyright (C) 2006-2008 Marvell International Ltd.
+ * Eric Miao <eric.miao@marvell.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/module.h>
+#include <linux/kernel.h>
+#include <linux/init.h>
+#include <linux/platform_device.h>
+#include <linux/leds.h>
+#include <linux/i2c/da903x.h>
+
+#define DA9030_LED1_CONTROL 0x20
+#define DA9030_LED2_CONTROL 0x21
+#define DA9030_LED3_CONTROL 0x22
+#define DA9030_LED4_CONTROL 0x23
+#define DA9030_MISC_CONTROL_A 0x24 /* Vibrator Control */
+
+#define DA9034_LED1_CONTROL 0x35
+#define DA9034_LED2_CONTROL 0x36
+#define DA9034_VIBRA 0x40
+
+struct da903x_led {
+ struct led_classdev cdev;
+ struct work_struct work;
+ struct device *da903x_dev;
+ enum led_brightness new_brightness;
+ int id;
+ int flags;
+};
+
+static void da903x_led_work(struct work_struct *work)
+{
+ struct da903x_led *led = container_of(work, struct da903x_led, work);
+ struct device *da903x_dev = led->da903x_dev;
+ uint8_t val;
+ int offset;
+
+ switch (led->id) {
+ case DA9030_ID_LED_1:
+ case DA9030_ID_LED_2:
+ case DA9030_ID_LED_3:
+ case DA9030_ID_LED_4:
+ offset = led->id - DA9030_ID_LED_1;
+ val = led->flags & ~0x87;
+ val |= (led->new_brightness) ? 0x80 : 0; /* EN bit */
+ val |= (led->new_brightness >> 5) & 0x7; /* PWM<2:0> */
+ da903x_write(da903x_dev, DA9030_LED1_CONTROL + offset, val);
+ break;
+ case DA9030_ID_VIBRA:
+ val = led->flags & ~0x80;
+ val |= (led->new_brightness) ? 0x80 : 0; /* EN bit */
+ da903x_write(da903x_dev, DA9030_MISC_CONTROL_A, val);
+ break;
+ case DA9034_ID_LED_1:
+ case DA9034_ID_LED_2:
+ offset = led->id - DA9034_ID_LED_1;
+ val = (led->new_brightness * 0x5f / LED_FULL) & 0x7f;
+ val |= (led->flags & DA9034_LED_RAMP) ? 0x80 : 0;
+ da903x_write(da903x_dev, DA9034_LED1_CONTROL + offset, val);
+ break;
+ case DA9034_ID_VIBRA:
+ val = led->new_brightness & 0xfe;
+ da903x_write(da903x_dev, DA9034_VIBRA, val);
+ break;
+ }
+}
+
+static void da903x_led_set(struct led_classdev *led_cdev,
+ enum led_brightness value)
+{
+ struct da903x_led *led;
+
+ led = container_of(led_cdev, struct da903x_led, cdev);
+ led->new_brightness = value;
+ schedule_work(&led->work);
+}
+
+static int __devinit da903x_led_probe(struct platform_device *pdev)
+{
+ struct led_info *pdata = pdev->dev.platform_data;
+ struct device *da903x_dev = pdev->dev.parent;
+ struct da903x_led *led;
+ int id, ret;
+
+ if (pdata == NULL)
+ return 0;
+
+ id = pdev->id;
+
+ if (!((id >= DA9030_ID_LED_1 && id <= DA9030_ID_VIBRA) ||
+ (id >= DA9034_ID_LED_1 && id <= DA9034_ID_VIBRA))) {
+ dev_err(&pdev->dev, "invalid LED ID (%d) specified\n", id);
+ return -EINVAL;
+ }
+
+ led = kzalloc(sizeof(struct da903x_led), GFP_KERNEL);
+ if (led == NULL) {
+ dev_err(&pdev->dev, "failed to alloc memory for LED%d\n", id);
+ return -ENOMEM;
+ }
+
+ led->cdev.name = pdata->name;
+ led->cdev.default_trigger = pdata->default_trigger;
+ led->cdev.brightness_set = da903x_led_set;
+ led->cdev.brightness = LED_OFF;
+
+ led->id = id;
+ led->flags = pdata->flags;
+ led->da903x_dev = da903x_dev;
+ led->new_brightness = LED_OFF;
+
+ INIT_WORK(&led->work, da903x_led_work);
+
+ ret = led_classdev_register(&pdev->dev, &led->cdev);
+ if (ret) {
+ dev_err(&pdev->dev, "failed to register LED %d\n", id);
+ goto err;
+ }
+
+ platform_set_drvdata(pdev, led);
+ return 0;
+
+err:
+ kfree(led);
+ return ret;
+}
+
+static int __devexit da903x_led_remove(struct platform_device *pdev)
+{
+ struct da903x_led *led = platform_get_drvdata(pdev);
+
+ led_classdev_unregister(&led->cdev);
+ kfree(led);
+ return 0;
+}
+
+static struct platform_driver da903x_led_driver = {
+ .driver = {
+ .name = "da903x-led",
+ .owner = THIS_MODULE,
+ },
+ .probe = da903x_led_probe,
+ .remove = __devexit_p(da903x_led_remove),
+};
+
+static int __init da903x_led_init(void)
+{
+ return platform_driver_register(&da903x_led_driver);
+}
+module_init(da903x_led_init);
+
+static void __exit da903x_led_exit(void)
+{
+ platform_driver_unregister(&da903x_led_driver);
+}
+module_exit(da903x_led_exit);
+
+MODULE_DESCRIPTION("LEDs driver for Dialog Semiconductor DA9030/DA9034");
+MODULE_AUTHOR("Eric Miao <eric.miao@marvell.com>"
+ "Mike Rapoport <mike@compulab.co.il>");
+MODULE_LICENSE("GPL");
+MODULE_ALIAS("platform:da903x-led");
--
1.5.4.3
[-- Warning: decoded text below may be mangled, UTF-8 assumed --]
[-- Attachment #4: 0003-backlight-add-DA9030-DA9034-WLED-based-backlight-su.patch --]
[-- Type: text/x-diff; name=0003-backlight-add-DA9030-DA9034-WLED-based-backlight-su.patch, Size: 7183 bytes --]
From 33584e9f85c02e61afe0bfb6cb89269cc859e4f8 Mon Sep 17 00:00:00 2001
From: Eric Miao <eric.miao@marvell.com>
Date: Thu, 14 Aug 2008 17:09:59 +0800
Subject: [PATCH] backlight: add DA9030/DA9034 WLED-based backlight support
Signed-off-by: Mike Rapoport <mike@compulab.il.co>
Signed-off-by: Eric Miao <eric.miao@marvell.com>
---
drivers/video/backlight/Kconfig | 7 ++
drivers/video/backlight/Makefile | 1 +
drivers/video/backlight/da903x.c | 202 ++++++++++++++++++++++++++++++++++++++
3 files changed, 210 insertions(+), 0 deletions(-)
create mode 100644 drivers/video/backlight/da903x.c
diff --git a/drivers/video/backlight/Kconfig b/drivers/video/backlight/Kconfig
index 452b770..f50a03e 100644
--- a/drivers/video/backlight/Kconfig
+++ b/drivers/video/backlight/Kconfig
@@ -156,6 +156,13 @@ config BACKLIGHT_PWM
If you have a LCD backlight adjustable by PWM, say Y to enable
this driver.
+config BACKLIGHT_DA903X
+ tristate "Backlight Driver for DA9030/DA9034 using WLED"
+ depends on BACKLIGHT_CLASS_DEVICE && PMIC_DA903X
+ help
+ If you have a LCD backlight connected to the WLED output of DA9030
+ or DA9034 WLED output, say Y here to enable this driver.
+
config BACKLIGHT_MBP_NVIDIA
tristate "MacBook Pro Nvidia Backlight Driver"
depends on BACKLIGHT_CLASS_DEVICE && X86
diff --git a/drivers/video/backlight/Makefile b/drivers/video/backlight/Makefile
index b405aac..a6ce5d3 100644
--- a/drivers/video/backlight/Makefile
+++ b/drivers/video/backlight/Makefile
@@ -15,5 +15,6 @@ obj-$(CONFIG_BACKLIGHT_OMAP1) += omap1_bl.o
obj-$(CONFIG_BACKLIGHT_PROGEAR) += progear_bl.o
obj-$(CONFIG_BACKLIGHT_CARILLO_RANCH) += cr_bllcd.o
obj-$(CONFIG_BACKLIGHT_PWM) += pwm_bl.o
+obj-$(CONFIG_BACKLIGHT_DA903X) += da903x.o
obj-$(CONFIG_BACKLIGHT_MBP_NVIDIA) += mbp_nvidia_bl.o
diff --git a/drivers/video/backlight/da903x.c b/drivers/video/backlight/da903x.c
new file mode 100644
index 0000000..d900c64
--- /dev/null
+++ b/drivers/video/backlight/da903x.c
@@ -0,0 +1,202 @@
+/*
+ * Backlight driver for Dialog Semiconductor DA9030/DA9034
+ *
+ * Copyright (C) 2008 Compulab, Ltd.
+ * Mike Rapoport <mike@compulab.co.il>
+ *
+ * Copyright (C) 2006-2008 Marvell International Ltd.
+ * Eric Miao <eric.miao@marvell.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/platform_device.h>
+#include <linux/fb.h>
+#include <linux/backlight.h>
+#include <linux/i2c/da903x.h>
+
+#define DA9030_WLED_CONTROL 0x25
+#define DA9030_WLED_CP_EN (1 << 6)
+#define DA9030_WLED_TRIM(x) ((x) & 0x7)
+
+#define DA9034_WLED_CONTROL1 0x3C
+#define DA9034_WLED_CONTROL2 0x3D
+
+#define DA9034_WLED_BOOST_EN (1 << 5)
+
+#define DA9030_MAX_BRIGHTNESS 8
+#define DA9034_MAX_BRIGHTNESS 0x7f
+
+struct da903x_backlight_data {
+ struct device *da903x_dev;
+ int id;
+ int current_brightness;
+};
+
+static int da903x_backlight_set(struct backlight_device *bl, int brightness)
+{
+ struct da903x_backlight_data *data = bl_get_data(bl);
+ struct device *dev = data->da903x_dev;
+ uint8_t val;
+ int ret = 0;
+
+ switch (data->id) {
+ case DA9034_ID_WLED:
+ ret = da903x_update(dev, DA9034_WLED_CONTROL1,
+ brightness, 0x7f);
+ if (ret)
+ return ret;
+
+ if (data->current_brightness && brightness == 0)
+ ret = da903x_clr_bit(dev,
+ DA9034_WLED_CONTROL2,
+ DA9034_WLED_BOOST_EN);
+
+ if (data->current_brightness == 0 && brightness)
+ ret = da903x_set_bit(dev,
+ DA9034_WLED_CONTROL2,
+ DA9034_WLED_BOOST_EN);
+ break;
+ case DA9030_ID_WLED:
+ val = DA9030_WLED_TRIM(brightness - 1);
+ val |= brightness ? DA9030_WLED_CP_EN : 0;
+ ret = da903x_write(dev, DA9030_WLED_CONTROL, val);
+ break;
+ }
+
+ if (ret)
+ return ret;
+
+ data->current_brightness = brightness;
+ return 0;
+}
+
+static int da903x_backlight_update_status(struct backlight_device *bl)
+{
+ int brightness = bl->props.brightness;
+
+ if (bl->props.power != FB_BLANK_UNBLANK)
+ brightness = 0;
+
+ if (bl->props.fb_blank != FB_BLANK_UNBLANK)
+ brightness = 0;
+
+ return da903x_backlight_set(bl, brightness);
+}
+
+static int da903x_backlight_get_brightness(struct backlight_device *bl)
+{
+ struct da903x_backlight_data *data = bl_get_data(bl);
+ return data->current_brightness;
+}
+
+static struct backlight_ops da903x_backlight_ops = {
+ .update_status = da903x_backlight_update_status,
+ .get_brightness = da903x_backlight_get_brightness,
+};
+
+static int da903x_backlight_probe(struct platform_device *pdev)
+{
+ struct da903x_backlight_data *data;
+ struct backlight_device *bl;
+ int id, max_brightness;
+
+ data = kzalloc(sizeof(*data), GFP_KERNEL);
+ if (data == NULL)
+ return -ENOMEM;
+
+ id = pdev->id;
+
+ switch (id) {
+ case DA9030_ID_WLED:
+ max_brightness = DA9030_MAX_BRIGHTNESS;
+ break;
+ case DA9034_ID_WLED:
+ max_brightness = DA9034_MAX_BRIGHTNESS;
+ break;
+ default:
+ dev_err(&pdev->dev, "invalid backlight device ID(%d)\n", id);
+ return -EINVAL;
+ }
+
+ data->da903x_dev = pdev->dev.parent;
+ data->id = id;
+ data->current_brightness = 0;
+
+ bl = backlight_device_register(pdev->name, &pdev->dev,
+ data, &da903x_backlight_ops);
+ if (IS_ERR(bl)) {
+ dev_err(&pdev->dev, "failed to register backlight\n");
+ return PTR_ERR(bl);
+ }
+
+ bl->props.max_brightness = max_brightness;
+ bl->props.brightness = max_brightness;
+
+ platform_set_drvdata(pdev, bl);
+ backlight_update_status(bl);
+ return 0;
+}
+
+static int da903x_backlight_remove(struct platform_device *pdev)
+{
+ struct backlight_device *bl = platform_get_drvdata(pdev);
+ struct da903x_backlight_data *data = bl_get_data(bl);
+
+ backlight_device_unregister(bl);
+ kfree(data);
+ return 0;
+}
+
+#ifdef CONFIG_PM
+static int da903x_backlight_suspend(struct platform_device *pdev,
+ pm_message_t state)
+{
+ struct backlight_device *bl = platform_get_drvdata(pdev);
+ return da903x_backlight_set(bl, 0);
+}
+
+static int da903x_backlight_resume(struct platform_device *pdev)
+{
+ struct backlight_device *bl = platform_get_drvdata(pdev);
+
+ backlight_update_status(bl);
+ return 0;
+}
+#else
+#define da903x_backlight_suspend NULL
+#define da903x_backlight_resume NULL
+#endif
+
+static struct platform_driver da903x_backlight_driver = {
+ .driver = {
+ .name = "da903x-backlight",
+ .owner = THIS_MODULE,
+ },
+ .probe = da903x_backlight_probe,
+ .remove = da903x_backlight_remove,
+ .suspend = da903x_backlight_suspend,
+ .resume = da903x_backlight_resume,
+};
+
+static int __init da903x_backlight_init(void)
+{
+ return platform_driver_register(&da903x_backlight_driver);
+}
+module_init(da903x_backlight_init);
+
+static void __exit da903x_backlight_exit(void)
+{
+ platform_driver_unregister(&da903x_backlight_driver);
+}
+module_exit(da903x_backlight_exit);
+
+MODULE_DESCRIPTION("Backlight Driver for Dialog Semiconductor DA9030/DA9034");
+MODULE_AUTHOR("Eric Miao <eric.miao@marvell.com>"
+ "Mike Rapoport <mike@compulab.co.il>");
+MODULE_LICENSE("GPL");
+MODULE_ALIAS("platform:da903x-backlight");
--
1.5.4.3
[-- Warning: decoded text below may be mangled, UTF-8 assumed --]
[-- Attachment #5: 0004-input-add-touchscreen-interface-found-on-DA9034-PMI.patch --]
[-- Type: text/x-diff; name=0004-input-add-touchscreen-interface-found-on-DA9034-PMI.patch, Size: 11166 bytes --]
From 0b559eebca31dbcd6a3eb938d5fa1a91e855a4e4 Mon Sep 17 00:00:00 2001
From: Eric Miao <eric.miao@marvell.com>
Date: Thu, 14 Aug 2008 17:24:28 +0800
Subject: [PATCH] input: add touchscreen interface found on DA9034 PMIC
Signed-off-by: Eric Miao <eric.miao@marvell.com>
---
drivers/input/touchscreen/Kconfig | 8 +
drivers/input/touchscreen/Makefile | 1 +
drivers/input/touchscreen/da9034.c | 380 ++++++++++++++++++++++++++++++++++++
3 files changed, 389 insertions(+), 0 deletions(-)
create mode 100644 drivers/input/touchscreen/da9034.c
diff --git a/drivers/input/touchscreen/Kconfig b/drivers/input/touchscreen/Kconfig
index 6e60a97..1f4c449 100644
--- a/drivers/input/touchscreen/Kconfig
+++ b/drivers/input/touchscreen/Kconfig
@@ -54,6 +54,14 @@ config TOUCHSCREEN_CORGI
To compile this driver as a module, choose M here: the
module will be called corgi_ts.
+config TOUCHSCREEN_DA9034
+ tristate "Touchscreen support for Dialog Semiconductor DA9034"
+ depends on PMIC_DA903X
+ default y
+ help
+ Say Y here to enable the support for the touchscreen found
+ on Dialog Semiconductor DA9034 PMIC.
+
config TOUCHSCREEN_FUJITSU
tristate "Fujitsu serial touchscreen"
select SERIO
diff --git a/drivers/input/touchscreen/Makefile b/drivers/input/touchscreen/Makefile
index 15cf290..c408600 100644
--- a/drivers/input/touchscreen/Makefile
+++ b/drivers/input/touchscreen/Makefile
@@ -27,6 +27,7 @@ obj-$(CONFIG_TOUCHSCREEN_TOUCHRIGHT) += touchright.o
obj-$(CONFIG_TOUCHSCREEN_TOUCHWIN) += touchwin.o
obj-$(CONFIG_TOUCHSCREEN_UCB1400) += ucb1400_ts.o
obj-$(CONFIG_TOUCHSCREEN_WM97XX) += wm97xx-ts.o
+obj-$(CONFIG_TOUCHSCREEN_DA9034) += da9034.o
wm97xx-ts-$(CONFIG_TOUCHSCREEN_WM9705) += wm9705.o
wm97xx-ts-$(CONFIG_TOUCHSCREEN_WM9712) += wm9712.o
wm97xx-ts-$(CONFIG_TOUCHSCREEN_WM9713) += wm9713.o
diff --git a/drivers/input/touchscreen/da9034.c b/drivers/input/touchscreen/da9034.c
new file mode 100644
index 0000000..ab54a6a
--- /dev/null
+++ b/drivers/input/touchscreen/da9034.c
@@ -0,0 +1,380 @@
+/*
+ * Touchscreen driver for Dialog Semiconductor DA9034
+ *
+ * Copyright (C) 2006-2008 Marvell International Ltd.
+ * Fengwei Yin <fengwei.yin@marvell.com>
+ * Eric Miao <eric.miao@marvell.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/module.h>
+#include <linux/kernel.h>
+#include <linux/init.h>
+#include <linux/delay.h>
+#include <linux/platform_device.h>
+#include <linux/input.h>
+#include <linux/i2c/da903x.h>
+
+#define DA9034_MANUAL_CTRL 0x50
+#define DA9034_LDO_ADC_EN (1 << 4)
+
+#define DA9034_AUTO_CTRL1 0x51
+
+#define DA9034_AUTO_CTRL2 0x52
+#define DA9034_AUTO_TSI_EN (1 << 3)
+#define DA9034_PEN_DETECT (1 << 4)
+
+#define DA9034_TSI_CTRL1 0x53
+#define DA9034_TSI_CTRL2 0x54
+#define DA9034_TSI_X_MSB 0x6c
+#define DA9034_TSI_Y_MSB 0x6d
+#define DA9034_TSI_XY_LSB 0x6e
+
+enum {
+ STATE_IDLE, /* wait for pendown */
+ STATE_BUSY, /* TSI busy sampling */
+ STATE_STOP, /* sample available */
+ STATE_WAIT, /* Wait to start next sample */
+};
+
+enum {
+ EVENT_PEN_DOWN,
+ EVENT_PEN_UP,
+ EVENT_TSI_READY,
+ EVENT_TIMEDOUT,
+};
+
+struct da9034_touch {
+ struct device *da9034_dev;
+ struct input_dev *input_dev;
+
+ struct delayed_work tsi_work;
+ struct notifier_block notifier;
+
+ int state;
+
+ int interval_ms;
+ int x_inverted;
+ int y_inverted;
+
+ int last_x;
+ int last_y;
+};
+
+static inline int is_pen_down(struct da9034_touch *touch)
+{
+ return da903x_query_status(touch->da9034_dev, DA9034_STATUS_PEN_DOWN);
+}
+
+static inline int detect_pen_down(struct da9034_touch *touch, int on)
+{
+ if (on)
+ return da903x_set_bit(touch->da9034_dev,
+ DA9034_AUTO_CTRL2, DA9034_PEN_DETECT);
+ else
+ return da903x_clr_bit(touch->da9034_dev,
+ DA9034_AUTO_CTRL2, DA9034_PEN_DETECT);
+}
+
+static int read_tsi(struct da9034_touch *touch)
+{
+ uint8_t _x, _y, _v;
+ int ret;
+
+ ret = da903x_read(touch->da9034_dev, DA9034_TSI_X_MSB, &_x);
+ if (ret)
+ return ret;
+
+ ret = da903x_read(touch->da9034_dev, DA9034_TSI_Y_MSB, &_y);
+ if (ret)
+ return ret;
+
+ ret = da903x_read(touch->da9034_dev, DA9034_TSI_XY_LSB, &_v);
+ if (ret)
+ return ret;
+
+ touch->last_x = ((_x << 2) & 0x3fc) | (_v & 0x3);
+ touch->last_y = ((_y << 2) & 0x3fc) | ((_v & 0xc) >> 2);
+ return 0;
+}
+
+static inline int start_tsi(struct da9034_touch *touch)
+{
+ return da903x_set_bit(touch->da9034_dev,
+ DA9034_AUTO_CTRL2, DA9034_AUTO_TSI_EN);
+}
+
+static inline int stop_tsi(struct da9034_touch *touch)
+{
+ return da903x_clr_bit(touch->da9034_dev,
+ DA9034_AUTO_CTRL2, DA9034_AUTO_TSI_EN);
+}
+
+static inline void report_pen_down(struct da9034_touch *touch)
+{
+ int x = touch->last_x;
+ int y = touch->last_y;
+
+ x &= 0xfff; x = touch->x_inverted ? (1024 - x) : x;
+ y &= 0xfff; y = touch->y_inverted ? (1024 - y) : y;
+
+ input_report_abs(touch->input_dev, ABS_X, x);
+ input_report_abs(touch->input_dev, ABS_Y, y);
+ input_report_abs(touch->input_dev, ABS_PRESSURE, 255);
+ input_report_key(touch->input_dev, BTN_TOUCH, 1);
+
+ input_sync(touch->input_dev);
+}
+
+static inline void report_pen_up(struct da9034_touch *touch)
+{
+ input_report_abs(touch->input_dev, ABS_PRESSURE, 0);
+ input_report_key(touch->input_dev, BTN_TOUCH, 0);
+
+ input_sync(touch->input_dev);
+}
+
+static void da9034_event_handler(struct da9034_touch *touch, int event)
+{
+ int err = 0;
+
+ switch (touch->state) {
+ case STATE_IDLE:
+ if (event != EVENT_PEN_DOWN)
+ break;
+
+ /* Enable auto measurement of the TSI, this will
+ * automatically disable pen down detection
+ */
+ err = start_tsi(touch);
+ if (err)
+ goto err_reset;
+
+ touch->state = STATE_BUSY;
+ break;
+
+ case STATE_BUSY:
+ if (event != EVENT_TSI_READY)
+ break;
+
+ err = read_tsi(touch);
+ if (err)
+ goto err_reset;
+
+ /* Disable auto measurement of the TSI, so that
+ * pen down status will be available
+ */
+ err = stop_tsi(touch);
+ if (err)
+ goto err_reset;
+
+ touch->state = STATE_STOP;
+ break;
+
+ case STATE_STOP:
+ if (event == EVENT_PEN_DOWN) {
+ report_pen_down(touch);
+ schedule_delayed_work(&touch->tsi_work,
+ msecs_to_jiffies(touch->interval_ms));
+ touch->state = STATE_WAIT;
+ }
+
+ if (event == EVENT_PEN_UP) {
+ report_pen_up(touch);
+ touch->state = STATE_IDLE;
+ }
+
+ input_sync(touch->input_dev);
+ break;
+
+ case STATE_WAIT:
+ if (event != EVENT_TIMEDOUT)
+ break;
+
+ if (is_pen_down(touch)) {
+ start_tsi(touch);
+ touch->state = STATE_BUSY;
+ } else
+ touch->state = STATE_IDLE;
+ break;
+ }
+ return;
+
+err_reset:
+ touch->state = STATE_IDLE;
+ stop_tsi(touch);
+ detect_pen_down(touch, 1);
+}
+
+static void da9034_tsi_work(struct work_struct *work)
+{
+ struct da9034_touch *touch =
+ container_of(work, struct da9034_touch, tsi_work.work);
+
+ da9034_event_handler(touch, EVENT_TIMEDOUT);
+}
+
+static int da9034_touch_notifier(struct notifier_block *nb,
+ unsigned long event, void *data)
+{
+ struct da9034_touch *touch =
+ container_of(nb, struct da9034_touch, notifier);
+
+ if (event & DA9034_EVENT_PEN_DOWN) {
+ if (is_pen_down(touch))
+ da9034_event_handler(touch, EVENT_PEN_DOWN);
+ else
+ da9034_event_handler(touch, EVENT_PEN_UP);
+ }
+
+ if (event & DA9034_EVENT_TSI_READY)
+ da9034_event_handler(touch, EVENT_TSI_READY);
+
+ return 0;
+}
+
+static int da9034_touch_open(struct input_dev *dev)
+{
+ struct da9034_touch *touch = input_get_drvdata(dev);
+ int ret;
+
+ ret = da903x_register_notifier(touch->da9034_dev, &touch->notifier,
+ DA9034_EVENT_PEN_DOWN | DA9034_EVENT_TSI_READY);
+ if (ret)
+ return -EBUSY;
+
+ /* Enable ADC LDO */
+ ret = da903x_set_bit(touch->da9034_dev,
+ DA9034_MANUAL_CTRL, DA9034_LDO_ADC_EN);
+ if (ret)
+ return ret;
+
+ /* TSI_DELAY: 3 slots, TSI_SKIP: 3 slots */
+ ret = da903x_write(touch->da9034_dev, DA9034_TSI_CTRL1, 0x1b);
+ if (ret)
+ return ret;
+
+ ret = da903x_write(touch->da9034_dev, DA9034_TSI_CTRL2, 0x00);
+ if (ret)
+ return ret;
+
+ touch->state = STATE_IDLE;
+ detect_pen_down(touch, 1);
+ return 0;
+}
+
+static void da9034_touch_close(struct input_dev *dev)
+{
+ struct da9034_touch *touch = input_get_drvdata(dev);
+
+ da903x_unregister_notifier(touch->da9034_dev, &touch->notifier,
+ DA9034_EVENT_PEN_DOWN | DA9034_EVENT_TSI_READY);
+
+ cancel_delayed_work_sync(&touch->tsi_work);
+
+ touch->state = STATE_IDLE;
+ stop_tsi(touch);
+ detect_pen_down(touch, 0);
+
+ /* Disable ADC LDO */
+ da903x_clr_bit(touch->da9034_dev,
+ DA9034_MANUAL_CTRL, DA9034_LDO_ADC_EN);
+}
+
+
+static int __devinit da9034_touch_probe(struct platform_device *pdev)
+{
+ struct da9034_touch_pdata *pdata = pdev->dev.platform_data;
+ struct da9034_touch *touch;
+ struct input_dev *input_dev;
+ int ret = 0;
+
+ touch = kzalloc(sizeof(struct da9034_touch), GFP_KERNEL);
+ if (touch == NULL) {
+ dev_err(&pdev->dev, "failed to allocate driver data\n");
+ return -ENOMEM;
+ }
+
+ touch->da9034_dev = pdev->dev.parent;
+ touch->interval_ms = pdata->interval_ms;
+ touch->x_inverted = pdata->x_inverted;
+ touch->y_inverted = pdata->y_inverted;
+
+ INIT_DELAYED_WORK(&touch->tsi_work, da9034_tsi_work);
+ touch->notifier.notifier_call = da9034_touch_notifier;
+
+ input_dev = input_allocate_device();
+ if (!input_dev) {
+ dev_err(&pdev->dev, "failed to allocate input device\n");
+ ret = -ENOMEM;
+ goto err_free_touch;
+ }
+
+ input_dev->name = pdev->name;
+ input_dev->open = da9034_touch_open;
+ input_dev->close = da9034_touch_close;
+ input_dev->dev.parent = &pdev->dev;
+
+ set_bit(EV_ABS, input_dev->evbit);
+ set_bit(ABS_X, input_dev->absbit);
+ set_bit(ABS_Y, input_dev->absbit);
+ set_bit(ABS_PRESSURE, input_dev->absbit);
+
+ input_set_abs_params(input_dev, ABS_X, 0, 1023, 0, 0);
+ input_set_abs_params(input_dev, ABS_Y, 0, 1023, 0, 0);
+ input_set_abs_params(input_dev, ABS_PRESSURE, 0, 255, 0, 0);
+
+ touch->input_dev = input_dev;
+ input_set_drvdata(input_dev, touch);
+
+ ret = input_register_device(input_dev);
+ if (ret)
+ goto err_free_input;
+
+ platform_set_drvdata(pdev, touch);
+ return 0;
+
+err_free_input:
+ input_free_device(input_dev);
+err_free_touch:
+ kfree(touch);
+ return ret;
+}
+
+static int __devexit da9034_touch_remove(struct platform_device *pdev)
+{
+ struct da9034_touch *touch = platform_get_drvdata(pdev);
+
+ input_free_device(touch->input_dev);
+ kfree(touch);
+ return 0;
+}
+
+static struct platform_driver da9034_touch_driver = {
+ .driver = {
+ .name = "da9034-touch",
+ .owner = THIS_MODULE,
+ },
+ .probe = da9034_touch_probe,
+ .remove = da9034_touch_remove,
+};
+
+static int __init da9034_touch_init(void)
+{
+ return platform_driver_register(&da9034_touch_driver);
+}
+module_init(da9034_touch_init);
+
+static void __exit da9034_touch_exit(void)
+{
+ platform_driver_unregister(&da9034_touch_driver);
+}
+module_exit(da9034_touch_exit);
+
+MODULE_DESCRIPTION("Touchscreen driver for Dialog Semiconductor DA9034");
+MODULE_AUTHOR("Eric Miao <eric.miao@marvell.com>");
+MODULE_LICENSE("GPL");
+MODULE_ALIAS("platform:da9034-touch");
--
1.5.4.3
^ permalink raw reply [flat|nested] 10+ messages in thread
* Re: Fundamental Design Flaw of the Device Driver Model?
2008-08-22 6:25 Fundamental Design Flaw of the Device Driver Model? Eric Miao
2008-08-22 9:03 ` Pavel Machek
@ 2008-08-22 11:17 ` Liam Girdwood
2008-08-22 14:00 ` Stefan Richter
2008-08-22 14:33 ` Jon Smirl
3 siblings, 0 replies; 10+ messages in thread
From: Liam Girdwood @ 2008-08-22 11:17 UTC (permalink / raw)
To: Eric Miao; +Cc: LKML, Pavel Machek
On Fri, 2008-08-22 at 14:25 +0800, Eric Miao wrote:
> Fundamental Design Flaw of the Device Driver Model?
> ===================================================
>
> Sorry for the misleading subject, its purpose is to draw your attention :-)
> The ideas below are preliminary and I hope I'm not making serious mistakes
> here.
>
> This question has actually been around in my mind for several months, when
> I started to work on some devices with multiple functions. Specifically, a
> Power Management IC (PMIC in short in the following text) usually includes
> LEDs support (charging, indication...) audio, touch screen, power monitoring,
> LDOs, DC-DC bucks, and possibly some others.
>
> The initial two ideas came into my mind were:
>
> 1. separate the functions into multiple devices, write a driver for each
> of these devices
>
I've opted for this option with the WM8350 PMIC driver.
> This, however, creates many questions you have to face with:
>
> 1. on what bus shall these sub-devices be?
> ** this is the reason I choose to use "platform_device", at least they
> can reside on the platform_bus_type, thus platform_driver can be used
> for this sub-device
My WM8350 clients are all platform_devices too.
>
> 2. these devices are actually useless except for linking the sub-device
> to it's sub-device driver and of course, wasting memory. Normally in
> the driver, another dedicated device will be created. E.g. let's take
> a typical simple LED driver as an example:
>
They are not useless as most of my client devices _need_ some sort of
platform data to be passed on for their probe() e.g. my LED driver needs
to know it's brightness values, trigger etc. They also all need a PMIC
reference (passed in the platform_data) which they will need to call any
core PMIC functions e.g. chip read/write
>
> 3. Who should be the correct parent of these LED devices, the intermediate
> sub-device we created just now? Or the pmic device on the I2C bus?
> My answer is the the latter, obviously. However, writing code like:
>
> led_classdev_register(pdev->dev.parent, &led_cdev)
Fwiw, I've made the WM8350 I2C driver the parent. The I2C driver also
contains the core PMIC services e.g. IRQs, GPIO's, IO, etc
> A normal device layout would be:
>
> device specific
> virtual bus type
> |
> platform_bus_type i2c_bus_type | virtual devices
> | | | |
> (device) V V V V
> Platform BUS ---> I2C Controller ---> PMIC -+-> LED device (1)
> |
> +-> LED device (2)
> |
> +-> LED device (3)
> |
> +-> DC-DC Buck1
> |
> +-> DC-DC Buck2
> |
> +-> LDO1
> |
> +-> LDO2
> |
> +-> Backlight PWM1
> |
> +-> Backlight PWM2
> |
> ...
>
Ok, I basically have :-
Platform devices
| |
V V
Platform Bus --> I2C controller --> PMIC +-> LED1
+-> LED2
+-> Audio
+-> DCDC1
+-> Backlight
I originally had a PMIC bus (like above) but decided it was easier just
to use the existing kernel infrastructure. My I2C PMIC driver just
registers the client platform_devices when it probes.
Cheers
Liam
^ permalink raw reply [flat|nested] 10+ messages in thread
* Re: Fundamental Design Flaw of the Device Driver Model?
2008-08-22 9:32 ` Eric Miao
@ 2008-08-22 11:29 ` pHilipp Zabel
2008-08-23 0:56 ` Eric Miao
0 siblings, 1 reply; 10+ messages in thread
From: pHilipp Zabel @ 2008-08-22 11:29 UTC (permalink / raw)
To: Eric Miao; +Cc: Pavel Machek, LKML, Samuel Ortiz
On Fri, Aug 22, 2008 at 11:32 AM, Eric Miao <eric.y.miao@gmail.com> wrote:
> On Fri, Aug 22, 2008 at 5:03 PM, Pavel Machek <pavel@suse.cz> wrote:
>>> Fundamental Design Flaw of the Device Driver Model?
>>> ===================================================
>>>
>>> Sorry for the misleading subject, its purpose is to draw your attention :-)
>>> The ideas below are preliminary and I hope I'm not making serious mistakes
>>> here.
>>>
>>> This question has actually been around in my mind for several months, when
>>> I started to work on some devices with multiple functions. Specifically, a
>>> Power Management IC (PMIC in short in the following text) usually includes
>>> LEDs support (charging, indication...) audio, touch screen, power monitoring,
>>> LDOs, DC-DC bucks, and possibly some others.
>>>
>>> The initial two ideas came into my mind were:
>>>
>>> 1. separate the functions into multiple devices, write a driver for each
>>> of these devices
>>
>> Go for 1.
>>
>>> 4. An intermediate device with no bus, no driver, no many other things
>>> is really not something deserving a "struct device", that's a waste
>>> of memory.
>>
>> Memory is not _that_ expensive, and struct device is not that
>> big. Adding infrastructure to driver model for supporting this would
>> also cost you memory, this time in .text segment.
>>
>
> Actually I don't mind wasting additional memory for a better structure,
> but option (1) isn't. And I just assume you have read to the end of thi
> mail, then you will see it's more like a concept change,no fundamental
> change to the code itself.
>
> Actually the reason to have two different kind of devices are obvious:
>
> 1. physical device - handles the bus related operations (read, write,
> locking, I/O)
> 2. virtual device - handles the functionality (interface with the
> upper framework)
>
> With this separation, the same IP on different semiconductor can be
> shared naturally. There are lot of such products esp in embedded market,
> with multiple shared IPs within a single die.
>
> Attached is a patch series to get you guys a feeling how the option
> (1) will look like, and think again about the device-driver relationship,
> the correctness of introducing an intermediate platform_device.
Instead of allocating/adding the platform subdevices manually in
da903x_add_subdevs, couldn't you use the MFD (multi function device)
infrastructure that seems to be intended for this case? Also, I think
that the da9030 base driver would then better fit in drivers/mfd, not
drivers/i2c/chips (didn't Jean Delvare himself state that ideally
drivers/i2c/chips should be empty?).
regards
Philipp
^ permalink raw reply [flat|nested] 10+ messages in thread
* Re: Fundamental Design Flaw of the Device Driver Model?
2008-08-22 6:25 Fundamental Design Flaw of the Device Driver Model? Eric Miao
2008-08-22 9:03 ` Pavel Machek
2008-08-22 11:17 ` Liam Girdwood
@ 2008-08-22 14:00 ` Stefan Richter
2008-08-22 14:33 ` Jon Smirl
3 siblings, 0 replies; 10+ messages in thread
From: Stefan Richter @ 2008-08-22 14:00 UTC (permalink / raw)
To: Eric Miao; +Cc: LKML, Liam Girdwood, pHilipp Zabel
Eric Miao wrote:
> I'd really like to blame the device model that prevent me from writing a
> perfect driver for such multi-function devices, otherwise I'd like to
> blame the semiconductor manufacturers to come up with such devices making
> us write ugly drivers. But I couldn't:
>
> 1. the PMIC design is nature, if I were the PMIC designer, with so many
> analog circuitry already built-in, I would also add ADC, Touch, Audio
> PWM, and many others to make them squeeze into the tiny chip. (No,
> not including using I2C bus, which I'd prefer to avoid)
>
> 2. There's no fundamental flaw in the device model (sorry for the title),
> of course, after weeks of reviewing the underlying code.
>
> So probably, we are writing drivers in an incorrect way, that we have
> ignored for the following reasons:
>
> 1. Most Linux developers are working on the PC world, which usually don't
> have to care about the multi-function devices, one PCI card, one PCI
> device, and one PCI device one function, we have a PCI driver to handle
> that, in that PCI driver, implement the function we need, that's all.
>
> 2. Even if there are multiple sub-devices, they are probably of the same
> functionality, and creating an array is easy, managing them in a whole
> in the driver is really not a big deal.
>
> 3. We don't strictly distinguish between a physical device and a virtual
> device, a physical device driver and a virtual device driver. E.g. a
> virtual device could be a "net_device", "mtd_device", "led_classdev",
> and many others
I didn't read and understand all of your post. Nevertheless I dare to
comment on these last 3 points here, because they don't fully match what
we already have been dealing with for a long time now. For example,
SCSI (including newer transport types over serial buses or networks)
deals with target devices which have children called logical units.
Logical units of the same target can have very different functionality
(= implement different command sets) while there is still a dependency
of the logical units on the target which provides them. Other example:
USB presumably deals with multi-function devices with some inner
structure and dependency too. (I don't know details.)
Ditto FireWire: There are "nodes" on a bus, and each node can have 0..n
"units". The units can implement various different protocols. During
the presence of a node on the bus, units on it can come and go at any
time. (Our drivers handle this in a simplified manner though.) The
ieee1394 driver stack and its future replacement, the firewire stack,
instantiate a struct device for each node, and a struct device for each
unit. There are appropriate parent-child relationships between them.
(Furthermore, node devices are children of the respective bus controller
device.)
All these devices have the same bus_type. The older ieee1394 core
distinguishes between node devices and unit devices by means of
different class devices. The newer, leaner firewire core distinguishes
between them by means of different device_type.
There are high-level drivers which are bound to unit devices, but not to
node devices. These drivers have the necessary knowledge of the
hierarchy though because their business with a unit device typically
also involves some dealings with the parent and grandparent.
(Types of the new stack: struct fw_device and struct fw_unit in
drivers/firewire/fw-device.[hc],
types of the old stack: struct node_entry and struct unit_directory in
drivers/ieee1394/nodemgr.[hc])
Anyway; there are several approaches to multi-function devices possible.
Wherever you go, the goal should be to keep it simple. (The ieee1394
stack is not a good example for this; the firewire stack is better in
this regard.) It typically makes a lot of sense to create struct device
based objects _not_ based on which boundaries the silicon draws, but
which functionality is there. E.g. if you can operate the various
functions of a hardware by means of separate drivers that don't
interfere a lot with each other (a core layer may control such
interferences), then separate struct device embedding objects for each
functionality are a good idea.
And you may not even have to resort to the term "virtual" when talking
about devices or device types or bus types if you structure it all
nicely and reasonably simple.
--
Stefan Richter
-=====-==--- =--- =-==-
http://arcgraph.de/sr/
^ permalink raw reply [flat|nested] 10+ messages in thread
* Re: Fundamental Design Flaw of the Device Driver Model?
2008-08-22 6:25 Fundamental Design Flaw of the Device Driver Model? Eric Miao
` (2 preceding siblings ...)
2008-08-22 14:00 ` Stefan Richter
@ 2008-08-22 14:33 ` Jon Smirl
2008-08-23 1:02 ` Eric Miao
3 siblings, 1 reply; 10+ messages in thread
From: Jon Smirl @ 2008-08-22 14:33 UTC (permalink / raw)
To: Eric Miao; +Cc: LKML
On 8/22/08, Eric Miao <eric.y.miao@gmail.com> wrote:
======================================================
> This, however, creates many questions you have to face with:
>
> 1. on what bus shall these sub-devices be?
> ** this is the reason I choose to use "platform_device", at least they
> can reside on the platform_bus_type, thus platform_driver can be used
> for this sub-device
Another option is making your own bus. If I understand your hardware
it effectively has an internal bus.
--
Jon Smirl
jonsmirl@gmail.com
^ permalink raw reply [flat|nested] 10+ messages in thread
* Re: Fundamental Design Flaw of the Device Driver Model?
2008-08-22 11:29 ` pHilipp Zabel
@ 2008-08-23 0:56 ` Eric Miao
0 siblings, 0 replies; 10+ messages in thread
From: Eric Miao @ 2008-08-23 0:56 UTC (permalink / raw)
To: pHilipp Zabel; +Cc: Pavel Machek, LKML, Samuel Ortiz
> Instead of allocating/adding the platform subdevices manually in
> da903x_add_subdevs, couldn't you use the MFD (multi function device)
> infrastructure that seems to be intended for this case? Also, I think
> that the da9030 base driver would then better fit in drivers/mfd, not
> drivers/i2c/chips (didn't Jean Delvare himself state that ideally
> drivers/i2c/chips should be empty?).
Actually, I don't mind keep it anywhere. The intermediate
platform_device is what I'd like to avoid mostly. Using my
own simple logic allows me to do that later.
^ permalink raw reply [flat|nested] 10+ messages in thread
* Re: Fundamental Design Flaw of the Device Driver Model?
2008-08-22 14:33 ` Jon Smirl
@ 2008-08-23 1:02 ` Eric Miao
2008-08-23 3:54 ` Jon Smirl
0 siblings, 1 reply; 10+ messages in thread
From: Eric Miao @ 2008-08-23 1:02 UTC (permalink / raw)
To: Jon Smirl; +Cc: LKML
On Fri, Aug 22, 2008 at 10:33 AM, Jon Smirl <jonsmirl@gmail.com> wrote:
> On 8/22/08, Eric Miao <eric.y.miao@gmail.com> wrote:
> ======================================================
>> This, however, creates many questions you have to face with:
>>
>> 1. on what bus shall these sub-devices be?
>> ** this is the reason I choose to use "platform_device", at least they
>> can reside on the platform_bus_type, thus platform_driver can be used
>> for this sub-device
>
> Another option is making your own bus. If I understand your hardware
> it effectively has an internal bus.
>
That's another option around, but it didn't solve my fundamental question
of, (e.g. an PCI card with multiple network interfaces and other functionality):
Why should I have to create an intermediate device provided that a
"struct net_device" already contains a "struct device"? And that
device-driver binding, parameter passing (platform_data), bus and
other functionalities of this "struct net_device" is not used while
that's used solely by that intermediate device (platform_device maybe)?
They should have perfectly been combined into a single virtual device.
^ permalink raw reply [flat|nested] 10+ messages in thread
* Re: Fundamental Design Flaw of the Device Driver Model?
2008-08-23 1:02 ` Eric Miao
@ 2008-08-23 3:54 ` Jon Smirl
0 siblings, 0 replies; 10+ messages in thread
From: Jon Smirl @ 2008-08-23 3:54 UTC (permalink / raw)
To: Eric Miao; +Cc: LKML
On 8/22/08, Eric Miao <eric.y.miao@gmail.com> wrote:
> On Fri, Aug 22, 2008 at 10:33 AM, Jon Smirl <jonsmirl@gmail.com> wrote:
> > On 8/22/08, Eric Miao <eric.y.miao@gmail.com> wrote:
> > ======================================================
> >> This, however, creates many questions you have to face with:
> >>
> >> 1. on what bus shall these sub-devices be?
> >> ** this is the reason I choose to use "platform_device", at least they
> >> can reside on the platform_bus_type, thus platform_driver can be used
> >> for this sub-device
> >
> > Another option is making your own bus. If I understand your hardware
> > it effectively has an internal bus.
> >
>
>
> That's another option around, but it didn't solve my fundamental question
> of, (e.g. an PCI card with multiple network interfaces and other functionality):
>
> Why should I have to create an intermediate device provided that a
> "struct net_device" already contains a "struct device"? And that
> device-driver binding, parameter passing (platform_data), bus and
> other functionalities of this "struct net_device" is not used while
> that's used solely by that intermediate device (platform_device maybe)?
>
> They should have perfectly been combined into a single virtual device.
>
First device represent the hardware with the PCI interface for the
card. Instantiating it creates a bus for the card. You then auto add
devices to this bus for each of the sub-devices on the card. Auto add
is fine in this case since the sub-devices aren't optional.
A multifunction card really is a local private bus with multiple
devices on it. First device is a real device - it's the bus controller
for the multifunction card.
A stranger problem is encountered with audio hardware. The SOC CPU
code loads an i2s/ac97 driver. A generic driver for the audio codec is
also loaded. Now you have to create a strange "fabric" device which
represent the specific PCB the generic codec and SOC have been
soldered into. The fabric driver describes how the codec and CPU are
wired together and if all of the codec functions are brought out to
jacks. For example, you don't want to display an ALSA capture device
if the codec supports it but no mic-in jack has been soldered to the
PCB. Is the fabric device a real device? You can't program it but you
can't get rid of it either.
--
Jon Smirl
jonsmirl@gmail.com
^ permalink raw reply [flat|nested] 10+ messages in thread
end of thread, other threads:[~2008-08-23 3:55 UTC | newest]
Thread overview: 10+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2008-08-22 6:25 Fundamental Design Flaw of the Device Driver Model? Eric Miao
2008-08-22 9:03 ` Pavel Machek
2008-08-22 9:32 ` Eric Miao
2008-08-22 11:29 ` pHilipp Zabel
2008-08-23 0:56 ` Eric Miao
2008-08-22 11:17 ` Liam Girdwood
2008-08-22 14:00 ` Stefan Richter
2008-08-22 14:33 ` Jon Smirl
2008-08-23 1:02 ` Eric Miao
2008-08-23 3:54 ` Jon Smirl
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox
Powered by JetHome