#include #include #include #include #include #include #include #include #include #include #include #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 delayed_work detect_work; bool detect_disabled; bool sleeve_changed; bool sleeve_present; bool sleeve_wakeup; struct gpio_desc *detect; 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) { struct device *mdev = bus_find_device_by_name(&platform_bus_type, NULL, "ipaq-h3xxx-micro"); struct ipaq_micro *micro; struct ipaq_micro_msg msg; memset(buf, 0, len); if (!mdev || !mdev->driver) return -EINVAL; micro = dev_get_drvdata(mdev); if (!micro) return -EINVAL; memset(&msg, 0, sizeof(msg)); while (len) { size_t this_len = len; if (this_len > 8) this_len = 8; msg.id = MSG_SPI_READ; msg.tx_len = 3; msg.tx_data[0] = addr; msg.tx_data[1] = addr >> 8; msg.tx_data[2] = this_len; ipaq_micro_tx_msg_sync(micro, &msg); memcpy(buf, msg.rx_data, msg.rx_len); buf += msg.rx_len; len -= msg.rx_len; addr += msg.rx_len; } return 0; } static char *ipaq_option_get_string(u8 **ptr, u8 *end) { 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 */ gpiod_set_value_cansleep(opt->nvram_on, 1); /* Wait for power to stabilise */ msleep(SLEEVE_NVRAM_POWER_MS); /* Read the first few bytes from the NVRAM */ ret = ipaq_option_spi_read(header, 0, sizeof(header)); if (ret) goto err; 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; /* 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) { 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 (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) { gpiod_set_value(opt->opt_reset, 1); gpiod_set_value(opt->opt_on, 0); } static void ipaq_option_insert(struct opt_info *opt) { int ret; ret = ipaq_option_read_id(opt, &opt->id); if (ret) return; dev_info(opt->dev, "%s sleeve inserted\n", opt->id.description); if (opt->id.opt_on_ctl) ipaq_option_power_on(opt); if (!opt->sleeve_present) ipaq_option_device_add(opt->dev, opt->id.id, opt->id.description); opt->sleeve_present = true; } static void ipaq_option_remove(struct opt_info *opt) { if (opt->sleeve_present) ipaq_option_device_del(); 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) { struct opt_info *opt = container_of(work, struct opt_info, detect_work.work); int present = gpiod_get_value_cansleep(opt->detect); if (opt->sleeve_changed) ipaq_option_remove(opt); if (present) ipaq_option_insert(opt); else ipaq_option_remove(opt); } static irqreturn_t ipaq_option_irq(int irq, void *data) { struct opt_info *opt = data; dev_info(opt->dev, "irq: option pack: %u\n", gpiod_get_value(opt->detect)); if (!opt->detect_disabled) mod_delayed_work(system_wq, &opt->detect_work, msecs_to_jiffies(100)); return IRQ_HANDLED; } static int ipaq_h3xxx_sleeve_probe(struct platform_device *pdev) { struct device *dev = &pdev->dev; struct opt_info *opt; int irq, ret; opt = devm_kzalloc(dev, sizeof(*opt), GFP_KERNEL); if (!opt) return -ENOMEM; platform_set_drvdata(pdev, opt); opt->dev = dev; INIT_DELAYED_WORK(&opt->detect_work, ipaq_option_work); opt->nvram_on = devm_gpiod_get(dev, "nvram-on", GPIOD_OUT_LOW); if (IS_ERR(opt->nvram_on)) return PTR_ERR(opt->nvram_on); opt->opt_on = devm_gpiod_get(dev, "option-on", GPIOD_OUT_LOW); if (IS_ERR(opt->opt_on)) return PTR_ERR(opt->opt_on); opt->opt_reset = devm_gpiod_get(dev, "option-reset", GPIOD_OUT_LOW); if (IS_ERR(opt->opt_reset)) return PTR_ERR(opt->opt_reset); opt->detect = devm_gpiod_get_optional(dev, "option-detect", GPIOD_IN); if (IS_ERR(opt->detect)) return PTR_ERR(opt->detect); if (opt->detect) { irq = gpiod_to_irq(opt->detect); if (irq < 0) return irq; ret = devm_request_irq(dev, irq, ipaq_option_irq, IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING, dev_name(dev), opt); if (ret) return ret; } 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; } static int ipaq_h3xxx_sleeve_remove(struct platform_device *pdev) { struct opt_info *opt = platform_get_drvdata(pdev); opt->detect_disabled = true; cancel_delayed_work_sync(&opt->detect_work); ipaq_option_remove(opt); return 0; } static int ipaq_h3xxx_check_wakeup(struct device *dev, void *data) { if (device_may_wakeup(dev)) return 1; return device_for_each_child(dev, data, ipaq_h3xxx_check_wakeup); } static int ipaq_h3xxx_sleeve_suspend(struct device *dev) { struct opt_info *opt = dev_get_drvdata(dev); int may_wakeup; opt->detect_disabled = true; cancel_delayed_work_sync(&opt->detect_work); /* Disable sleeve power if device isn't a wakeup source */ may_wakeup = device_for_each_child(dev, NULL, ipaq_h3xxx_check_wakeup); opt->sleeve_wakeup = may_wakeup; if (device_may_wakeup(dev)) enable_irq_wake(gpiod_to_irq(opt->detect)); return 0; } static int ipaq_h3xxx_sleeve_suspend_noirq(struct device *dev) { struct opt_info *opt = dev_get_drvdata(dev); if (!opt->sleeve_wakeup && opt->sleeve_present) { ipaq_option_power_off(opt, true); /* * Give the power time to collapse before bringing the * reset signal to logic 0. */ mdelay(1); gpiod_set_raw_value(opt->opt_reset, 0); } return 0; } static int ipaq_h3xxx_sleeve_resume_noirq(struct device *dev) { struct opt_info *opt = dev_get_drvdata(dev); if (!opt->sleeve_wakeup && gpiod_get_value(opt->detect)) { /* * We can't check the ID of the sleeve here, so assume that * 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); } return 0; } static int ipaq_h3xxx_sleeve_resume(struct device *dev) { struct opt_info *opt = dev_get_drvdata(dev); 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", ret); opt->sleeve_changed = true; goto finish; } if (memcmp(opt->id.nvram, id.nvram, sizeof(id.nvram))) { /* sleeve changed */ dev_warn(opt->dev, "sleeve changed\n"); opt->sleeve_changed = true; } else { opt->detect_disabled = false; return 0; } } finish: opt->detect_disabled = false; schedule_delayed_work(&opt->detect_work, 0); flush_delayed_work(&opt->detect_work); return 0; } static struct dev_pm_ops ipaq_h3xxx_sleeve_pm = { SET_NOIRQ_SYSTEM_SLEEP_PM_OPS(ipaq_h3xxx_sleeve_suspend_noirq, ipaq_h3xxx_sleeve_resume_noirq) SET_SYSTEM_SLEEP_PM_OPS(ipaq_h3xxx_sleeve_suspend, ipaq_h3xxx_sleeve_resume) }; static struct platform_driver ipaq_h3xxx_sleeve = { .driver = { .name = "ipaq-h3xxx-sleeve", .pm = &ipaq_h3xxx_sleeve_pm, }, .probe = ipaq_h3xxx_sleeve_probe, .remove = ipaq_h3xxx_sleeve_remove, }; module_platform_driver(ipaq_h3xxx_sleeve); MODULE_ALIAS("platform:ipaq-h3xxx-sleeve"); MODULE_LICENSE("GPL");