summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--arch/arm/mach-sa1100/h3xxx-sleeve.c189
-rw-r--r--arch/arm/mach-sa1100/ipaq-sleeve-bus.c27
-rw-r--r--include/linux/ipaq-sleeve.h4
3 files changed, 192 insertions, 28 deletions
diff --git a/arch/arm/mach-sa1100/h3xxx-sleeve.c b/arch/arm/mach-sa1100/h3xxx-sleeve.c
index ed0edaf43d9c..8aef18062e40 100644
--- a/arch/arm/mach-sa1100/h3xxx-sleeve.c
+++ b/arch/arm/mach-sa1100/h3xxx-sleeve.c
@@ -1,3 +1,4 @@
+#include <linux/ctype.h>
#include <linux/delay.h>
#include <linux/gpio/consumer.h>
#include <linux/interrupt.h>
@@ -7,13 +8,31 @@
#include <linux/slab.h>
#include <linux/workqueue.h>
#include <linux/ipaq-sleeve.h>
+#include <asm/unaligned.h>
-#define SLEEVE_POWER_DELAY_MS 10
#define SLEEVE_NVRAM_POWER_MS 5
+enum {
+ MAX_HEADER = 256,
+ TYPE_I = 1,
+ TYPE_II = 2,
+ NO_RESET = 0xffff,
+};
+
+struct ipaq_sleeve_id {
+ struct ipaq_option_id id;
+ const char *description;
+ u8 version;
+ u8 extended_battery;
+ u16 reset_width;
+ u16 reset_wait;
+ u16 skt_type[2];
+ u8 opt_on_ctl;
+ u8 nvram[256];
+};
+
struct opt_info {
struct device *dev;
- struct ipaq_option_id id;
struct delayed_work detect_work;
bool detect_disabled;
bool sleeve_changed;
@@ -23,6 +42,8 @@ struct opt_info {
struct gpio_desc *nvram_on;
struct gpio_desc *opt_on;
struct gpio_desc *opt_reset;
+
+ struct ipaq_sleeve_id id;
};
static int ipaq_option_spi_read(void *buf, unsigned int addr, size_t len)
@@ -67,9 +88,54 @@ static int ipaq_option_spi_read(void *buf, unsigned int addr, size_t len)
return 0;
}
-static int ipaq_option_read_id(struct opt_info *opt, struct ipaq_option_id *id)
+static char *ipaq_option_get_string(u8 **ptr, u8 *end)
{
- __le16 vendor, device;
+ u8 *p = *ptr;
+ unsigned int i;
+
+ for (i = 0; p[i] && &p[i] < end; i++)
+ if (!isprint(p[i]) || &p[i] == end)
+ return NULL;
+
+ *ptr = p + i + 1;
+
+ return kstrndup(p, i, GFP_KERNEL);
+}
+
+static u8 *ipaq_option_get_ptr(u8 **ptr, u8 *end, size_t size)
+{
+ u8 *p = *ptr;
+
+ if (p - end < size)
+ return NULL;
+
+ *ptr += size;
+
+ return p;
+}
+
+static u8 ipaq_option_get_u8(u8 **ptr, u8 *end)
+{
+ u8 *p = ipaq_option_get_ptr(ptr, end, 1);
+ return p ? *p : 0;
+}
+
+static u16 ipaq_option_get_le16(u8 **ptr, u8 *end)
+{
+ u8 *p = ipaq_option_get_ptr(ptr, end, 2);
+ return p ? get_unaligned_le16(p) : 0;
+}
+
+static u32 ipaq_option_get_le32(u8 **ptr, u8 *end)
+{
+ u8 *p = ipaq_option_get_ptr(ptr, end, 4);
+ return p ? get_unaligned_le32(p) : 0;
+}
+
+static int ipaq_option_read_id(struct opt_info *opt, struct ipaq_sleeve_id *id)
+{
+ unsigned int length;
+ u8 header[5], *ptr, *end;
int ret;
/* Enable the CF bus */
@@ -78,32 +144,99 @@ static int ipaq_option_read_id(struct opt_info *opt, struct ipaq_option_id *id)
/* Wait for power to stabilise */
msleep(SLEEVE_NVRAM_POWER_MS);
- ret = ipaq_option_spi_read(&vendor, 6, 2);
+ /* Read the first few bytes from the NVRAM */
+ ret = ipaq_option_spi_read(header, 0, sizeof(header));
if (ret)
goto err;
- ret = ipaq_option_spi_read(&device, 8, 2);
+ ptr = header;
+ end = header + sizeof(header);
+ if (ipaq_option_get_u8(&ptr, end) != 0xaa) {
+ dev_err(opt->dev, "invalid ID structure\n");
+ ret = -EINVAL;
+ goto err;
+ }
+
+ length = ipaq_option_get_le32(&ptr, end);
+ if (length < sizeof(header) || length >= MAX_HEADER - 1) {
+ dev_err(opt->dev, "invalid ID length\n");
+ ret = -EINVAL;
+ goto err;
+ }
+
+ /* Read the full NVRAM contents */
+ ret = ipaq_option_spi_read(id->nvram, 0, length + 1);
if (ret)
goto err;
- id->vendor = le16_to_cpu(vendor);
- id->device = le16_to_cpu(device);
+ /* reset the pointer */
+ ptr = id->nvram + (ptr - header);
+ end = ptr + length + 1;
+
+ id->version = ipaq_option_get_u8(&ptr, end);
+ id->id.vendor = ipaq_option_get_le16(&ptr, end);
+ id->id.device = ipaq_option_get_le16(&ptr, end);
+
+ id->description = ipaq_option_get_string(&ptr, end);
+ if (!id->description) {
+ ret = -ENOMEM;
+ goto err;
+ }
+
+ ipaq_option_get_ptr(&ptr, end, 2); /* two reserved bytes */
+ id->extended_battery = ipaq_option_get_u8(&ptr, end);
+ ipaq_option_get_ptr(&ptr, end, 2); /* two reserved bytes */
+ id->reset_width = ipaq_option_get_le16(&ptr, end);
+ id->reset_wait = ipaq_option_get_le16(&ptr, end);
+ ipaq_option_get_le16(&ptr, end); /* bootstrap */
+ ipaq_option_get_le16(&ptr, end); /* OEM table */
+
+ if (id->version == TYPE_II) {
+ id->skt_type[0] = ipaq_option_get_le16(&ptr, end);
+ id->skt_type[1] = ipaq_option_get_le16(&ptr, end);
+ id->opt_on_ctl = ipaq_option_get_u8(&ptr, end);
+ } else {
+ id->skt_type[0] = 0;
+ id->skt_type[1] = 0;
+ id->opt_on_ctl = 1;
+ }
+
+ if (ipaq_option_get_le32(&ptr, end) != 0x0f0f0f0f) {
+ dev_err(opt->dev, "missing ID terminator\n");
+ ret = -EINVAL;
+ goto err;
+ }
err:
gpiod_set_value_cansleep(opt->nvram_on, 0);
return ret;
}
-static void ipaq_option_power_on(struct opt_info *opt, bool noirq)
+static void __ipaq_option_power_on(struct opt_info *opt, bool noirq)
{
gpiod_set_value(opt->opt_reset, 1);
gpiod_set_value(opt->opt_on, 1);
+
+ if (opt->id.reset_wait != NO_RESET) {
+ if (noirq)
+ mdelay(opt->id.reset_width);
+ else
+ msleep(opt->id.reset_width);
+ }
+
gpiod_set_value(opt->opt_reset, 0);
- if (noirq)
- mdelay(SLEEVE_POWER_DELAY_MS);
- else
- msleep(SLEEVE_POWER_DELAY_MS);
+ if (opt->id.reset_wait != NO_RESET) {
+ if (noirq)
+ mdelay(opt->id.reset_wait);
+ else
+ msleep(opt->id.reset_wait);
+ }
+}
+
+static void ipaq_option_power_on(struct opt_info *opt)
+{
+ __ipaq_option_power_on(opt, false);
}
static void ipaq_option_power_off(struct opt_info *opt, bool noirq)
@@ -120,13 +253,14 @@ static void ipaq_option_insert(struct opt_info *opt)
if (ret)
return;
- dev_info(opt->dev, "Option pack %04x:%04x detected\n",
- opt->id.vendor, opt->id.device);
+ dev_info(opt->dev, "%s sleeve inserted\n", opt->id.description);
- ipaq_option_power_on(opt, false);
+ if (opt->id.opt_on_ctl)
+ ipaq_option_power_on(opt);
if (!opt->sleeve_present)
- ipaq_option_device_add(opt->dev, opt->id);
+ ipaq_option_device_add(opt->dev, opt->id.id,
+ opt->id.description);
opt->sleeve_present = true;
}
@@ -137,6 +271,9 @@ static void ipaq_option_remove(struct opt_info *opt)
opt->sleeve_present = false;
ipaq_option_power_off(opt, false);
+
+ kfree(opt->id.description);
+ memset(&opt->id, 0, sizeof(opt->id));
}
static void ipaq_option_work(struct work_struct *work)
@@ -211,6 +348,9 @@ static int ipaq_h3xxx_sleeve_probe(struct platform_device *pdev)
schedule_delayed_work(&opt->detect_work, 0);
flush_delayed_work(&opt->detect_work);
+ device_set_wakeup_capable(dev, true);
+ device_set_wakeup_enable(dev, false);
+
return 0;
}
@@ -247,6 +387,9 @@ static int ipaq_h3xxx_sleeve_suspend(struct device *dev)
opt->sleeve_wakeup = may_wakeup;
+ if (device_may_wakeup(dev))
+ enable_irq_wake(gpiod_to_irq(opt->detect));
+
return 0;
}
@@ -278,7 +421,7 @@ static int ipaq_h3xxx_sleeve_resume_noirq(struct device *dev)
* it is the same sleeve inserted, and power it up. This
* avoids an eject/insert cycle on any inserted PCMCIA card.
*/
- ipaq_option_power_on(opt, true);
+ __ipaq_option_power_on(opt, true);
}
return 0;
@@ -287,10 +430,14 @@ static int ipaq_h3xxx_sleeve_resume_noirq(struct device *dev)
static int ipaq_h3xxx_sleeve_resume(struct device *dev)
{
struct opt_info *opt = dev_get_drvdata(dev);
- struct ipaq_option_id id;
+ struct ipaq_sleeve_id id;
int ret;
+ if (device_may_wakeup(dev))
+ disable_irq_wake(gpiod_to_irq(opt->detect));
+
if (opt->sleeve_present && gpiod_get_value_cansleep(opt->detect)) {
+ memset(&id, 0, sizeof(id));
ret = ipaq_option_read_id(opt, &id);
if (ret) {
dev_warn(opt->dev, "unable to read sleeve id: %d\n",
@@ -299,13 +446,11 @@ static int ipaq_h3xxx_sleeve_resume(struct device *dev)
goto finish;
}
- if (opt->id.vendor != id.vendor ||
- opt->id.device != id.device) {
+ if (memcmp(opt->id.nvram, id.nvram, sizeof(id.nvram))) {
/* sleeve changed */
dev_warn(opt->dev, "sleeve changed\n");
opt->sleeve_changed = true;
} else {
- dev_info(opt->dev, "same sleeve type inserted\n");
opt->detect_disabled = false;
return 0;
}
diff --git a/arch/arm/mach-sa1100/ipaq-sleeve-bus.c b/arch/arm/mach-sa1100/ipaq-sleeve-bus.c
index 9bf3201002b6..31299589d4a9 100644
--- a/arch/arm/mach-sa1100/ipaq-sleeve-bus.c
+++ b/arch/arm/mach-sa1100/ipaq-sleeve-bus.c
@@ -19,9 +19,17 @@ static ssize_t device_show(struct device *dev, struct device_attribute *attr, ch
}
static DEVICE_ATTR_RO(device);
+static ssize_t description_show(struct device *dev, struct device_attribute *attr, char *buf)
+{
+ struct ipaq_option_device *idev = to_ipaq_option_device(dev);
+ return snprintf(buf, PAGE_SIZE, "%s\n", idev->description);
+}
+static DEVICE_ATTR_RO(description);
+
static struct attribute *ipaq_option_sleeve_attrs[] = {
- &dev_attr_vendor.attr,
+ &dev_attr_description.attr,
&dev_attr_device.attr,
+ &dev_attr_vendor.attr,
NULL,
};
@@ -55,7 +63,8 @@ static int ipaq_option_sleeve_uevent(struct device *dev, struct kobj_uevent_env
if (!dev)
return -ENODEV;
- if (add_uevent_var(env, "MODALIAS=ipaq:v%04Xd%04X",
+ if (add_uevent_var(env, "DESCRIPTION=%s", idev->description) ||
+ add_uevent_var(env, "MODALIAS=ipaq:v%04Xd%04X",
idev->id.vendor, idev->id.device))
return -ENOMEM;
@@ -72,12 +81,14 @@ EXPORT_SYMBOL_GPL(ipaq_option_sleeve_bus);
static void ipaq_option_release(struct device *dev)
{
- struct ipaq_option_device *opt_dev = to_ipaq_option_device(dev);
+ struct ipaq_option_device *idev = to_ipaq_option_device(dev);
- kfree(opt_dev);
+ kfree(idev->description);
+ kfree(idev);
}
-int ipaq_option_device_add(struct device *parent, struct ipaq_option_id id)
+int ipaq_option_device_add(struct device *parent, struct ipaq_option_id id,
+ const char *description)
{
struct ipaq_option_device *idev;
@@ -85,6 +96,12 @@ int ipaq_option_device_add(struct device *parent, struct ipaq_option_id id)
if (!idev)
return -ENOMEM;
+ idev->description = kstrdup(description, GFP_KERNEL);
+ if (!idev->description) {
+ kfree(idev);
+ return -ENOMEM;
+ }
+
idev->id = id;
device_initialize(&idev->dev);
dev_set_name(&idev->dev, "sleeve");
diff --git a/include/linux/ipaq-sleeve.h b/include/linux/ipaq-sleeve.h
index fb669fad0c4e..42352e942c3c 100644
--- a/include/linux/ipaq-sleeve.h
+++ b/include/linux/ipaq-sleeve.h
@@ -6,6 +6,7 @@
struct ipaq_option_device {
struct ipaq_option_id id;
+ const char *description;
struct device dev;
};
#define to_ipaq_option_device(x) container_of(x, struct ipaq_option_device, dev)
@@ -17,7 +18,8 @@ struct ipaq_option_driver {
extern struct bus_type ipaq_option_sleeve_bus;
-int ipaq_option_device_add(struct device *parent, struct ipaq_option_id id);
+int ipaq_option_device_add(struct device *parent, struct ipaq_option_id id,
+ const char *description);
void ipaq_option_device_del(void);
static inline int ipaq_option_driver_register(struct ipaq_option_driver *drv)