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 +++++++++++++++++++++++++++++++----- 1 file changed, 167 insertions(+), 22 deletions(-) (limited to 'arch/arm/mach-sa1100/h3xxx-sleeve.c') 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; } -- cgit