#include #include #include #include #include #include #include #include #include #define SLEEVE_POWER_DELAY_MS 10 #define SLEEVE_NVRAM_POWER_MS 5 struct opt_info { struct device *dev; struct ipaq_option_id id; 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; }; 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 int ipaq_option_read_id(struct opt_info *opt, struct ipaq_option_id *id) { __le16 vendor, device; int ret; /* Enable the CF bus */ gpiod_set_value_cansleep(opt->nvram_on, 1); /* Wait for power to stabilise */ msleep(SLEEVE_NVRAM_POWER_MS); ret = ipaq_option_spi_read(&vendor, 6, 2); if (ret) goto err; ret = ipaq_option_spi_read(&device, 8, 2); if (ret) goto err; id->vendor = le16_to_cpu(vendor); id->device = le16_to_cpu(device); 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); gpiod_set_value(opt->opt_reset, 0); if (noirq) mdelay(SLEEVE_POWER_DELAY_MS); else msleep(SLEEVE_POWER_DELAY_MS); } 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, "Option pack %04x:%04x detected\n", opt->id.vendor, opt->id.device); ipaq_option_power_on(opt, false); if (!opt->sleeve_present) ipaq_option_device_add(opt->dev, opt->id); 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); } 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); 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; 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_option_id id; int ret; if (opt->sleeve_present && gpiod_get_value_cansleep(opt->detect)) { 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 (opt->id.vendor != id.vendor || opt->id.device != id.device) { /* 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; } } 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");