summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorRussell King <rmk+kernel@armlinux.org.uk>2016-09-15 01:38:27 +0100
committerRussell King <rmk+kernel@armlinux.org.uk>2020-10-12 21:56:12 +0100
commitb8ed2eca34fc3d57d2eadbc31c12a521fe08e221 (patch)
tree6eb543586555e588eed7808faef564bc6b46b1dd
parent6d2dbeb1d44af3c9eaf2e9cd429d0dd814aef7f5 (diff)
ARM: sa1100/h3xxx: sleeve support
Signed-off-by: Russell King <rmk+kernel@armlinux.org.uk>
-rw-r--r--arch/arm/mach-sa1100/Makefile2
-rw-r--r--arch/arm/mach-sa1100/h3600.c13
-rw-r--r--arch/arm/mach-sa1100/h3xxx-sleeve.c341
-rw-r--r--arch/arm/mach-sa1100/h3xxx.c6
-rw-r--r--arch/arm/mach-sa1100/ipaq-sleeve-bus.c124
-rw-r--r--arch/arm/mach-sa1100/ipaq-sleeve-cf.c70
-rw-r--r--arch/arm/mach-sa1100/ipaq-sleeve-dual.c68
-rw-r--r--drivers/pcmcia/sa1100_h3600.c60
-rw-r--r--include/linux/ipaq-sleeve.h34
9 files changed, 659 insertions, 59 deletions
diff --git a/arch/arm/mach-sa1100/Makefile b/arch/arm/mach-sa1100/Makefile
index 28c1cae0053f..80eb60109589 100644
--- a/arch/arm/mach-sa1100/Makefile
+++ b/arch/arm/mach-sa1100/Makefile
@@ -18,6 +18,8 @@ obj-$(CONFIG_SA1100_COLLIE) += collie.o
obj-$(CONFIG_SA1100_H3100) += h3100.o h3xxx.o
obj-$(CONFIG_SA1100_H3600) += h3600.o h3xxx.o
+obj-m += h3xxx-sleeve.o ipaq-sleeve-bus.o \
+ ipaq-sleeve-cf.o ipaq-sleeve-dual.o
obj-$(CONFIG_SA1100_HACKKIT) += hackkit.o
diff --git a/arch/arm/mach-sa1100/h3600.c b/arch/arm/mach-sa1100/h3600.c
index 5f4fb3027799..28ce2d1353c7 100644
--- a/arch/arm/mach-sa1100/h3600.c
+++ b/arch/arm/mach-sa1100/h3600.c
@@ -104,9 +104,22 @@ static struct gpiod_lookup_table h3600_irda_gpio_table = {
},
};
+static struct gpiod_lookup_table h3600_sleeve_gpio_table = {
+ .dev_id = "ipaq-h3xxx-sleeve",
+ .table = {
+ GPIO_LOOKUP("gpio", H3600_GPIO_OPT_DET,
+ "option-detect", GPIO_ACTIVE_LOW),
+ GPIO_LOOKUP("htc-egpio", 2, "option-reset", GPIO_ACTIVE_HIGH),
+ GPIO_LOOKUP("htc-egpio", 4, "nvram-on", GPIO_ACTIVE_HIGH),
+ GPIO_LOOKUP("htc-egpio", 5, "option-on", GPIO_ACTIVE_HIGH),
+ { },
+ },
+};
+
static void __init h3600_mach_init(void)
{
gpiod_add_lookup_table(&h3600_irda_gpio_table);
+ gpiod_add_lookup_table(&h3600_sleeve_gpio_table);
h3xxx_mach_init();
diff --git a/arch/arm/mach-sa1100/h3xxx-sleeve.c b/arch/arm/mach-sa1100/h3xxx-sleeve.c
new file mode 100644
index 000000000000..ed0edaf43d9c
--- /dev/null
+++ b/arch/arm/mach-sa1100/h3xxx-sleeve.c
@@ -0,0 +1,341 @@
+#include <linux/delay.h>
+#include <linux/gpio/consumer.h>
+#include <linux/interrupt.h>
+#include <linux/mfd/ipaq-micro.h>
+#include <linux/module.h>
+#include <linux/platform_device.h>
+#include <linux/slab.h>
+#include <linux/workqueue.h>
+#include <linux/ipaq-sleeve.h>
+
+#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");
diff --git a/arch/arm/mach-sa1100/h3xxx.c b/arch/arm/mach-sa1100/h3xxx.c
index baf62ef2ad70..9a44a31626b9 100644
--- a/arch/arm/mach-sa1100/h3xxx.c
+++ b/arch/arm/mach-sa1100/h3xxx.c
@@ -183,10 +183,16 @@ struct platform_device h3xxx_micro_asic = {
.num_resources = ARRAY_SIZE(h3xxx_micro_resources),
};
+static struct platform_device h3xxx_sleeve = {
+ .name = "ipaq-h3xxx-sleeve",
+ .id = -1,
+};
+
static struct platform_device *h3xxx_devices[] = {
&h3xxx_egpio,
&h3xxx_keys,
&h3xxx_micro_asic,
+ &h3xxx_sleeve,
};
static struct gpiod_lookup_table h3xxx_pcmcia_gpio_table = {
diff --git a/arch/arm/mach-sa1100/ipaq-sleeve-bus.c b/arch/arm/mach-sa1100/ipaq-sleeve-bus.c
new file mode 100644
index 000000000000..9bf3201002b6
--- /dev/null
+++ b/arch/arm/mach-sa1100/ipaq-sleeve-bus.c
@@ -0,0 +1,124 @@
+#include <linux/ipaq-sleeve.h>
+#include <linux/module.h>
+#include <linux/slab.h>
+
+#define to_ipaq_option_driver(x) \
+ container_of(x, struct ipaq_option_driver, driver)
+
+static ssize_t vendor_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, "%04x\n", idev->id.vendor);
+}
+static DEVICE_ATTR_RO(vendor);
+
+static ssize_t device_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, "%04x\n", idev->id.device);
+}
+static DEVICE_ATTR_RO(device);
+
+static struct attribute *ipaq_option_sleeve_attrs[] = {
+ &dev_attr_vendor.attr,
+ &dev_attr_device.attr,
+ NULL,
+};
+
+static const struct attribute_group ipaq_option_sleeve_dev_group = {
+ .attrs = ipaq_option_sleeve_attrs,
+};
+
+static const struct attribute_group *ipaq_option_sleeve_dev_groups[] = {
+ &ipaq_option_sleeve_dev_group,
+ NULL,
+};
+
+static int ipaq_option_sleeve_match(struct device *dev, struct device_driver *drv)
+{
+ struct ipaq_option_device *idev = to_ipaq_option_device(dev);
+ struct ipaq_option_driver *idrv = to_ipaq_option_driver(drv);
+ const struct ipaq_option_id *id;
+
+ for (id = idrv->id_table; id->vendor || id->device; id++)
+ if (id->vendor == idev->id.vendor &&
+ id->device == idev->id.device)
+ return 1;
+
+ return 0;
+}
+
+static int ipaq_option_sleeve_uevent(struct device *dev, struct kobj_uevent_env *env)
+{
+ struct ipaq_option_device *idev = to_ipaq_option_device(dev);
+
+ if (!dev)
+ return -ENODEV;
+
+ if (add_uevent_var(env, "MODALIAS=ipaq:v%04Xd%04X",
+ idev->id.vendor, idev->id.device))
+ return -ENOMEM;
+
+ return 0;
+}
+
+struct bus_type ipaq_option_sleeve_bus = {
+ .name = "ipaq-sleeve",
+ .dev_groups = ipaq_option_sleeve_dev_groups,
+ .match = ipaq_option_sleeve_match,
+ .uevent = ipaq_option_sleeve_uevent,
+};
+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);
+
+ kfree(opt_dev);
+}
+
+int ipaq_option_device_add(struct device *parent, struct ipaq_option_id id)
+{
+ struct ipaq_option_device *idev;
+
+ idev = kzalloc(sizeof(*idev), GFP_KERNEL);
+ if (!idev)
+ return -ENOMEM;
+
+ idev->id = id;
+ device_initialize(&idev->dev);
+ dev_set_name(&idev->dev, "sleeve");
+ idev->dev.parent = parent;
+ idev->dev.release = ipaq_option_release;
+ idev->dev.bus = &ipaq_option_sleeve_bus;
+
+ return device_add(&idev->dev);
+}
+EXPORT_SYMBOL_GPL(ipaq_option_device_add);
+
+static int ipaq_option_device_remove(struct device *dev, void *data)
+{
+ device_unregister(dev);
+ return 0;
+}
+
+void ipaq_option_device_del(void)
+{
+ bus_for_each_dev(&ipaq_option_sleeve_bus, NULL, NULL,
+ ipaq_option_device_remove);
+}
+EXPORT_SYMBOL_GPL(ipaq_option_device_del);
+
+static int ipaq_option_sleeve_init(void)
+{
+ return bus_register(&ipaq_option_sleeve_bus);
+}
+core_initcall(ipaq_option_sleeve_init);
+
+static void ipaq_option_sleeve_exit(void)
+{
+ bus_unregister(&ipaq_option_sleeve_bus);
+}
+module_exit(ipaq_option_sleeve_exit);
+
+MODULE_LICENSE("GPL");
diff --git a/arch/arm/mach-sa1100/ipaq-sleeve-cf.c b/arch/arm/mach-sa1100/ipaq-sleeve-cf.c
new file mode 100644
index 000000000000..8f0b8ae8b75c
--- /dev/null
+++ b/arch/arm/mach-sa1100/ipaq-sleeve-cf.c
@@ -0,0 +1,70 @@
+#include <linux/ipaq-sleeve.h>
+#include <linux/module.h>
+#include <linux/platform_device.h>
+#include <linux/slab.h>
+
+struct ipaq_sleeve_cf_data {
+ struct platform_device *skt;
+};
+
+static int ipaq_sleeve_cf_probe(struct device *dev)
+{
+ struct ipaq_option_device *idev = to_ipaq_option_device(dev);
+ struct ipaq_sleeve_cf_data *cf;
+ struct platform_device_info pinfo = {
+ .parent = dev,
+ .name = "sa11x0-pcmcia",
+ .id = 0,
+ .data = &idev->id,
+ .size_data = sizeof(idev->id),
+ };
+
+ cf = devm_kzalloc(dev, sizeof(*cf), GFP_KERNEL);
+ if (!cf)
+ return -ENOMEM;
+
+ cf->skt = platform_device_register_full(&pinfo);
+ if (IS_ERR(cf->skt)) {
+ dev_err(dev, "unable to register pcmcia device: %ld\n",
+ PTR_ERR(cf->skt));
+ return PTR_ERR(cf->skt);
+ }
+
+ dev_set_drvdata(dev, cf);
+
+ return 0;
+}
+
+static int ipaq_sleeve_cf_remove(struct device *dev)
+{
+ struct ipaq_sleeve_cf_data *cf = dev_get_drvdata(dev);
+
+ platform_device_unregister(cf->skt);
+
+ return 0;
+}
+
+static const struct ipaq_option_id ipaq_sleeve_cf_ids[] = {
+ { /* Compaq CompactFlash sleeve */
+ .vendor = 0x1125,
+ .device = 0xcf11,
+ }, { /* Compaq single PCMCIA sleeve */
+ .vendor = 0x1125,
+ .device = 0xd7c3,
+ }, { },
+};
+
+static struct ipaq_option_driver ipaq_sleeve_cf = {
+ .id_table = ipaq_sleeve_cf_ids,
+ .driver = {
+ .name = "ipaq-sleeve-cf",
+ .probe = ipaq_sleeve_cf_probe,
+ .remove = ipaq_sleeve_cf_remove,
+ },
+};
+
+module_driver(ipaq_sleeve_cf, ipaq_option_driver_register,
+ ipaq_option_driver_unregister);
+
+MODULE_LICENSE("GPL");
+MODULE_DEVICE_TABLE(ipaq, ipaq_sleeve_cf_ids);
diff --git a/arch/arm/mach-sa1100/ipaq-sleeve-dual.c b/arch/arm/mach-sa1100/ipaq-sleeve-dual.c
new file mode 100644
index 000000000000..dd4c3f756885
--- /dev/null
+++ b/arch/arm/mach-sa1100/ipaq-sleeve-dual.c
@@ -0,0 +1,68 @@
+#include <linux/ipaq-sleeve.h>
+#include <linux/module.h>
+#include <linux/platform_device.h>
+#include <linux/slab.h>
+
+struct ipaq_sleeve_dual_data {
+ struct platform_device *skt;
+};
+
+static int ipaq_sleeve_dual_probe(struct device *dev)
+{
+ struct ipaq_option_device *idev = to_ipaq_option_device(dev);
+ struct ipaq_sleeve_dual_data *cf;
+ struct platform_device_info pinfo = {
+ .parent = dev,
+ .name = "sa11x0-pcmcia",
+ .id = -1,
+ };
+
+ cf = devm_kzalloc(dev, sizeof(*cf), GFP_KERNEL);
+ if (!cf)
+ return -ENOMEM;
+
+ pinfo.data = &idev->id;
+ pinfo.size_data = sizeof(idev->id);
+
+ cf->skt = platform_device_register_full(&pinfo);
+ if (IS_ERR(cf->skt)) {
+ dev_err(dev, "unable to register pcmcia device: %ld\n",
+ PTR_ERR(cf->skt));
+ return PTR_ERR(cf->skt);
+ }
+
+ dev_set_drvdata(dev, cf);
+
+ return 0;
+}
+
+static int ipaq_sleeve_dual_remove(struct device *dev)
+{
+ struct ipaq_sleeve_dual_data *cf = dev_get_drvdata(dev);
+
+ platform_device_unregister(cf->skt);
+
+ return 0;
+}
+
+static const struct ipaq_option_id ipaq_sleeve_dual_ids[] = {
+ { /* Compaq dual PCMCIA sleeve */
+ .vendor = 0x1125,
+ .device = 0x0001,
+ }, { },
+};
+
+static struct ipaq_option_driver ipaq_sleeve_dual = {
+ .id_table = ipaq_sleeve_dual_ids,
+ .driver = {
+ .name = "ipaq-sleeve-dual",
+ .probe = ipaq_sleeve_dual_probe,
+ .remove = ipaq_sleeve_dual_remove,
+ },
+};
+
+module_driver(ipaq_sleeve_dual, ipaq_option_driver_register,
+ ipaq_option_driver_unregister);
+
+MODULE_LICENSE("GPL");
+MODULE_DEVICE_TABLE(ipaq, ipaq_sleeve_dual_ids);
diff --git a/drivers/pcmcia/sa1100_h3600.c b/drivers/pcmcia/sa1100_h3600.c
index a91222bc3824..a6d387d9ec4d 100644
--- a/drivers/pcmcia/sa1100_h3600.c
+++ b/drivers/pcmcia/sa1100_h3600.c
@@ -33,24 +33,6 @@ static int h3600_pcmcia_hw_init(struct soc_pcmcia_socket *skt)
switch (skt->nr) {
case 0:
- err = gpio_request(H3XXX_EGPIO_OPT_NVRAM_ON, "OPT NVRAM ON");
- if (err)
- goto err01;
- err = gpio_direction_output(H3XXX_EGPIO_OPT_NVRAM_ON, 0);
- if (err)
- goto err03;
- err = gpio_request(H3XXX_EGPIO_OPT_ON, "OPT ON");
- if (err)
- goto err03;
- err = gpio_direction_output(H3XXX_EGPIO_OPT_ON, 0);
- if (err)
- goto err04;
- err = gpio_request(H3XXX_EGPIO_OPT_RESET, "OPT RESET");
- if (err)
- goto err04;
- err = gpio_direction_output(H3XXX_EGPIO_OPT_RESET, 0);
- if (err)
- goto err05;
err = gpio_request(H3XXX_EGPIO_CARD_RESET, "PCMCIA CARD RESET");
if (err)
goto err05;
@@ -64,11 +46,7 @@ static int h3600_pcmcia_hw_init(struct soc_pcmcia_socket *skt)
return 0;
err06: gpio_free(H3XXX_EGPIO_CARD_RESET);
-err05: gpio_free(H3XXX_EGPIO_OPT_RESET);
-err04: gpio_free(H3XXX_EGPIO_OPT_ON);
-err03: gpio_free(H3XXX_EGPIO_OPT_NVRAM_ON);
-err01: gpio_free(H3XXX_GPIO_PCMCIA_IRQ0);
- return err;
+err05: return err;
}
static void h3600_pcmcia_hw_shutdown(struct soc_pcmcia_socket *skt)
@@ -76,14 +54,7 @@ static void h3600_pcmcia_hw_shutdown(struct soc_pcmcia_socket *skt)
switch (skt->nr) {
case 0:
/* Disable CF bus: */
- gpio_set_value(H3XXX_EGPIO_OPT_NVRAM_ON, 0);
- gpio_set_value(H3XXX_EGPIO_OPT_ON, 0);
- gpio_set_value(H3XXX_EGPIO_OPT_RESET, 1);
-
gpio_free(H3XXX_EGPIO_CARD_RESET);
- gpio_free(H3XXX_EGPIO_OPT_RESET);
- gpio_free(H3XXX_EGPIO_OPT_ON);
- gpio_free(H3XXX_EGPIO_OPT_NVRAM_ON);
break;
case 1:
break;
@@ -115,41 +86,12 @@ h3600_pcmcia_configure_socket(struct soc_pcmcia_socket *skt, const socket_state_
return 0;
}
-static void h3600_pcmcia_socket_init(struct soc_pcmcia_socket *skt)
-{
- /* Enable CF bus: */
- gpio_set_value(H3XXX_EGPIO_OPT_NVRAM_ON, 1);
- gpio_set_value(H3XXX_EGPIO_OPT_ON, 1);
- gpio_set_value(H3XXX_EGPIO_OPT_RESET, 0);
-
- msleep(10);
-}
-
-static void h3600_pcmcia_socket_suspend(struct soc_pcmcia_socket *skt)
-{
- /*
- * FIXME: This doesn't fit well. We don't have the mechanism in
- * the generic PCMCIA layer to deal with the idea of two sockets
- * on one bus. We rely on the cs.c behaviour shutting down
- * socket 0 then socket 1.
- */
- if (skt->nr == 1) {
- gpio_set_value(H3XXX_EGPIO_OPT_ON, 0);
- gpio_set_value(H3XXX_EGPIO_OPT_NVRAM_ON, 0);
- /* hmm, does this suck power? */
- gpio_set_value(H3XXX_EGPIO_OPT_RESET, 1);
- }
-}
-
struct pcmcia_low_level h3600_pcmcia_ops = {
.owner = THIS_MODULE,
.hw_init = h3600_pcmcia_hw_init,
.hw_shutdown = h3600_pcmcia_hw_shutdown,
.socket_state = h3600_pcmcia_socket_state,
.configure_socket = h3600_pcmcia_configure_socket,
-
- .socket_init = h3600_pcmcia_socket_init,
- .socket_suspend = h3600_pcmcia_socket_suspend,
};
int pcmcia_h3600_init(struct device *dev)
diff --git a/include/linux/ipaq-sleeve.h b/include/linux/ipaq-sleeve.h
new file mode 100644
index 000000000000..fb669fad0c4e
--- /dev/null
+++ b/include/linux/ipaq-sleeve.h
@@ -0,0 +1,34 @@
+#ifndef LINUX_IPAQ_SLEEVE_H
+#define LINUX_IPAQ_SLEEVE_H
+
+#include <linux/device.h>
+#include <linux/mod_devicetable.h>
+
+struct ipaq_option_device {
+ struct ipaq_option_id id;
+ struct device dev;
+};
+#define to_ipaq_option_device(x) container_of(x, struct ipaq_option_device, dev)
+
+struct ipaq_option_driver {
+ const struct ipaq_option_id *id_table;
+ struct device_driver driver;
+};
+
+extern struct bus_type ipaq_option_sleeve_bus;
+
+int ipaq_option_device_add(struct device *parent, struct ipaq_option_id id);
+void ipaq_option_device_del(void);
+
+static inline int ipaq_option_driver_register(struct ipaq_option_driver *drv)
+{
+ drv->driver.bus = &ipaq_option_sleeve_bus;
+ return driver_register(&drv->driver);
+}
+
+static inline void ipaq_option_driver_unregister(struct ipaq_option_driver *drv)
+{
+ driver_unregister(&drv->driver);
+}
+
+#endif