From 84a43257f88e48b2a64c10e09d131ef1d3a74df0 Mon Sep 17 00:00:00 2001 From: Russell King Date: Wed, 28 Sep 2016 09:55:58 +0100 Subject: ipaq sleeve updates Signed-off-by: Russell King --- arch/arm/mach-sa1100/h3xxx-sleeve.c | 189 +++++++++++++++++++++++++++++---- arch/arm/mach-sa1100/ipaq-sleeve-bus.c | 27 ++++- include/linux/ipaq-sleeve.h | 4 +- 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 #include #include #include @@ -7,13 +8,31 @@ #include #include #include +#include -#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) -- cgit