162306a36Sopenharmony_ci/*====================================================================== 262306a36Sopenharmony_ci 362306a36Sopenharmony_ci Device driver for the PCMCIA control functionality of StrongARM 462306a36Sopenharmony_ci SA-1100 microprocessors. 562306a36Sopenharmony_ci 662306a36Sopenharmony_ci The contents of this file are subject to the Mozilla Public 762306a36Sopenharmony_ci License Version 1.1 (the "License"); you may not use this file 862306a36Sopenharmony_ci except in compliance with the License. You may obtain a copy of 962306a36Sopenharmony_ci the License at http://www.mozilla.org/MPL/ 1062306a36Sopenharmony_ci 1162306a36Sopenharmony_ci Software distributed under the License is distributed on an "AS 1262306a36Sopenharmony_ci IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or 1362306a36Sopenharmony_ci implied. See the License for the specific language governing 1462306a36Sopenharmony_ci rights and limitations under the License. 1562306a36Sopenharmony_ci 1662306a36Sopenharmony_ci The initial developer of the original code is John G. Dorsey 1762306a36Sopenharmony_ci <john+@cs.cmu.edu>. Portions created by John G. Dorsey are 1862306a36Sopenharmony_ci Copyright (C) 1999 John G. Dorsey. All Rights Reserved. 1962306a36Sopenharmony_ci 2062306a36Sopenharmony_ci Alternatively, the contents of this file may be used under the 2162306a36Sopenharmony_ci terms of the GNU Public License version 2 (the "GPL"), in which 2262306a36Sopenharmony_ci case the provisions of the GPL are applicable instead of the 2362306a36Sopenharmony_ci above. If you wish to allow the use of your version of this file 2462306a36Sopenharmony_ci only under the terms of the GPL and not to allow others to use 2562306a36Sopenharmony_ci your version of this file under the MPL, indicate your decision 2662306a36Sopenharmony_ci by deleting the provisions above and replace them with the notice 2762306a36Sopenharmony_ci and other provisions required by the GPL. If you do not delete 2862306a36Sopenharmony_ci the provisions above, a recipient may use your version of this 2962306a36Sopenharmony_ci file under either the MPL or the GPL. 3062306a36Sopenharmony_ci 3162306a36Sopenharmony_ci======================================================================*/ 3262306a36Sopenharmony_ci 3362306a36Sopenharmony_ci#include <linux/module.h> 3462306a36Sopenharmony_ci#include <linux/gpio/consumer.h> 3562306a36Sopenharmony_ci#include <linux/init.h> 3662306a36Sopenharmony_ci#include <linux/regulator/consumer.h> 3762306a36Sopenharmony_ci#include <linux/slab.h> 3862306a36Sopenharmony_ci#include <linux/platform_device.h> 3962306a36Sopenharmony_ci 4062306a36Sopenharmony_ci#include <pcmcia/ss.h> 4162306a36Sopenharmony_ci 4262306a36Sopenharmony_ci#include <asm/hardware/scoop.h> 4362306a36Sopenharmony_ci 4462306a36Sopenharmony_ci#include "sa1100_generic.h" 4562306a36Sopenharmony_ci 4662306a36Sopenharmony_cistatic const char *sa11x0_cf_gpio_names[] = { 4762306a36Sopenharmony_ci [SOC_STAT_CD] = "detect", 4862306a36Sopenharmony_ci [SOC_STAT_BVD1] = "bvd1", 4962306a36Sopenharmony_ci [SOC_STAT_BVD2] = "bvd2", 5062306a36Sopenharmony_ci [SOC_STAT_RDY] = "ready", 5162306a36Sopenharmony_ci}; 5262306a36Sopenharmony_ci 5362306a36Sopenharmony_cistatic int sa11x0_cf_hw_init(struct soc_pcmcia_socket *skt) 5462306a36Sopenharmony_ci{ 5562306a36Sopenharmony_ci struct device *dev = skt->socket.dev.parent; 5662306a36Sopenharmony_ci int i; 5762306a36Sopenharmony_ci 5862306a36Sopenharmony_ci skt->gpio_reset = devm_gpiod_get(dev, "reset", GPIOD_OUT_HIGH); 5962306a36Sopenharmony_ci if (IS_ERR(skt->gpio_reset)) 6062306a36Sopenharmony_ci return PTR_ERR(skt->gpio_reset); 6162306a36Sopenharmony_ci 6262306a36Sopenharmony_ci skt->gpio_bus_enable = devm_gpiod_get_optional(dev, "bus-enable", 6362306a36Sopenharmony_ci GPIOD_OUT_HIGH); 6462306a36Sopenharmony_ci if (IS_ERR(skt->gpio_bus_enable)) 6562306a36Sopenharmony_ci return PTR_ERR(skt->gpio_bus_enable); 6662306a36Sopenharmony_ci 6762306a36Sopenharmony_ci skt->vcc.reg = devm_regulator_get_optional(dev, "vcc"); 6862306a36Sopenharmony_ci if (IS_ERR(skt->vcc.reg)) 6962306a36Sopenharmony_ci return PTR_ERR(skt->vcc.reg); 7062306a36Sopenharmony_ci 7162306a36Sopenharmony_ci if (!skt->vcc.reg) 7262306a36Sopenharmony_ci dev_warn(dev, 7362306a36Sopenharmony_ci "no Vcc regulator provided, ignoring Vcc controls\n"); 7462306a36Sopenharmony_ci 7562306a36Sopenharmony_ci for (i = 0; i < ARRAY_SIZE(sa11x0_cf_gpio_names); i++) { 7662306a36Sopenharmony_ci skt->stat[i].name = sa11x0_cf_gpio_names[i]; 7762306a36Sopenharmony_ci skt->stat[i].desc = devm_gpiod_get_optional(dev, 7862306a36Sopenharmony_ci sa11x0_cf_gpio_names[i], GPIOD_IN); 7962306a36Sopenharmony_ci if (IS_ERR(skt->stat[i].desc)) 8062306a36Sopenharmony_ci return PTR_ERR(skt->stat[i].desc); 8162306a36Sopenharmony_ci } 8262306a36Sopenharmony_ci return 0; 8362306a36Sopenharmony_ci} 8462306a36Sopenharmony_ci 8562306a36Sopenharmony_cistatic int sa11x0_cf_configure_socket(struct soc_pcmcia_socket *skt, 8662306a36Sopenharmony_ci const socket_state_t *state) 8762306a36Sopenharmony_ci{ 8862306a36Sopenharmony_ci return soc_pcmcia_regulator_set(skt, &skt->vcc, state->Vcc); 8962306a36Sopenharmony_ci} 9062306a36Sopenharmony_ci 9162306a36Sopenharmony_cistatic struct pcmcia_low_level sa11x0_cf_ops = { 9262306a36Sopenharmony_ci .owner = THIS_MODULE, 9362306a36Sopenharmony_ci .hw_init = sa11x0_cf_hw_init, 9462306a36Sopenharmony_ci .socket_state = soc_common_cf_socket_state, 9562306a36Sopenharmony_ci .configure_socket = sa11x0_cf_configure_socket, 9662306a36Sopenharmony_ci}; 9762306a36Sopenharmony_ci 9862306a36Sopenharmony_ciint __init pcmcia_collie_init(struct device *dev); 9962306a36Sopenharmony_ci 10062306a36Sopenharmony_cistatic int (*sa11x0_pcmcia_legacy_hw_init[])(struct device *dev) = { 10162306a36Sopenharmony_ci#ifdef CONFIG_SA1100_H3600 10262306a36Sopenharmony_ci pcmcia_h3600_init, 10362306a36Sopenharmony_ci#endif 10462306a36Sopenharmony_ci#ifdef CONFIG_SA1100_COLLIE 10562306a36Sopenharmony_ci pcmcia_collie_init, 10662306a36Sopenharmony_ci#endif 10762306a36Sopenharmony_ci}; 10862306a36Sopenharmony_ci 10962306a36Sopenharmony_cistatic int sa11x0_drv_pcmcia_legacy_probe(struct platform_device *dev) 11062306a36Sopenharmony_ci{ 11162306a36Sopenharmony_ci int i, ret = -ENODEV; 11262306a36Sopenharmony_ci 11362306a36Sopenharmony_ci /* 11462306a36Sopenharmony_ci * Initialise any "on-board" PCMCIA sockets. 11562306a36Sopenharmony_ci */ 11662306a36Sopenharmony_ci for (i = 0; i < ARRAY_SIZE(sa11x0_pcmcia_legacy_hw_init); i++) { 11762306a36Sopenharmony_ci ret = sa11x0_pcmcia_legacy_hw_init[i](&dev->dev); 11862306a36Sopenharmony_ci if (ret == 0) 11962306a36Sopenharmony_ci break; 12062306a36Sopenharmony_ci } 12162306a36Sopenharmony_ci 12262306a36Sopenharmony_ci return ret; 12362306a36Sopenharmony_ci} 12462306a36Sopenharmony_ci 12562306a36Sopenharmony_cistatic void sa11x0_drv_pcmcia_legacy_remove(struct platform_device *dev) 12662306a36Sopenharmony_ci{ 12762306a36Sopenharmony_ci struct skt_dev_info *sinfo = platform_get_drvdata(dev); 12862306a36Sopenharmony_ci int i; 12962306a36Sopenharmony_ci 13062306a36Sopenharmony_ci platform_set_drvdata(dev, NULL); 13162306a36Sopenharmony_ci 13262306a36Sopenharmony_ci for (i = 0; i < sinfo->nskt; i++) 13362306a36Sopenharmony_ci soc_pcmcia_remove_one(&sinfo->skt[i]); 13462306a36Sopenharmony_ci} 13562306a36Sopenharmony_ci 13662306a36Sopenharmony_cistatic int sa11x0_drv_pcmcia_probe(struct platform_device *pdev) 13762306a36Sopenharmony_ci{ 13862306a36Sopenharmony_ci struct soc_pcmcia_socket *skt; 13962306a36Sopenharmony_ci struct device *dev = &pdev->dev; 14062306a36Sopenharmony_ci 14162306a36Sopenharmony_ci if (pdev->id == -1) 14262306a36Sopenharmony_ci return sa11x0_drv_pcmcia_legacy_probe(pdev); 14362306a36Sopenharmony_ci 14462306a36Sopenharmony_ci skt = devm_kzalloc(dev, sizeof(*skt), GFP_KERNEL); 14562306a36Sopenharmony_ci if (!skt) 14662306a36Sopenharmony_ci return -ENOMEM; 14762306a36Sopenharmony_ci 14862306a36Sopenharmony_ci platform_set_drvdata(pdev, skt); 14962306a36Sopenharmony_ci 15062306a36Sopenharmony_ci skt->nr = pdev->id; 15162306a36Sopenharmony_ci skt->clk = devm_clk_get(dev, NULL); 15262306a36Sopenharmony_ci if (IS_ERR(skt->clk)) 15362306a36Sopenharmony_ci return PTR_ERR(skt->clk); 15462306a36Sopenharmony_ci 15562306a36Sopenharmony_ci sa11xx_drv_pcmcia_ops(&sa11x0_cf_ops); 15662306a36Sopenharmony_ci soc_pcmcia_init_one(skt, &sa11x0_cf_ops, dev); 15762306a36Sopenharmony_ci 15862306a36Sopenharmony_ci return sa11xx_drv_pcmcia_add_one(skt); 15962306a36Sopenharmony_ci} 16062306a36Sopenharmony_ci 16162306a36Sopenharmony_cistatic int sa11x0_drv_pcmcia_remove(struct platform_device *dev) 16262306a36Sopenharmony_ci{ 16362306a36Sopenharmony_ci struct soc_pcmcia_socket *skt; 16462306a36Sopenharmony_ci 16562306a36Sopenharmony_ci if (dev->id == -1) { 16662306a36Sopenharmony_ci sa11x0_drv_pcmcia_legacy_remove(dev); 16762306a36Sopenharmony_ci return 0; 16862306a36Sopenharmony_ci } 16962306a36Sopenharmony_ci 17062306a36Sopenharmony_ci skt = platform_get_drvdata(dev); 17162306a36Sopenharmony_ci 17262306a36Sopenharmony_ci soc_pcmcia_remove_one(skt); 17362306a36Sopenharmony_ci 17462306a36Sopenharmony_ci return 0; 17562306a36Sopenharmony_ci} 17662306a36Sopenharmony_ci 17762306a36Sopenharmony_cistatic struct platform_driver sa11x0_pcmcia_driver = { 17862306a36Sopenharmony_ci .driver = { 17962306a36Sopenharmony_ci .name = "sa11x0-pcmcia", 18062306a36Sopenharmony_ci }, 18162306a36Sopenharmony_ci .probe = sa11x0_drv_pcmcia_probe, 18262306a36Sopenharmony_ci .remove = sa11x0_drv_pcmcia_remove, 18362306a36Sopenharmony_ci}; 18462306a36Sopenharmony_ci 18562306a36Sopenharmony_ci/* sa11x0_pcmcia_init() 18662306a36Sopenharmony_ci * ^^^^^^^^^^^^^^^^^^^^ 18762306a36Sopenharmony_ci * 18862306a36Sopenharmony_ci * This routine performs low-level PCMCIA initialization and then 18962306a36Sopenharmony_ci * registers this socket driver with Card Services. 19062306a36Sopenharmony_ci * 19162306a36Sopenharmony_ci * Returns: 0 on success, -ve error code on failure 19262306a36Sopenharmony_ci */ 19362306a36Sopenharmony_cistatic int __init sa11x0_pcmcia_init(void) 19462306a36Sopenharmony_ci{ 19562306a36Sopenharmony_ci return platform_driver_register(&sa11x0_pcmcia_driver); 19662306a36Sopenharmony_ci} 19762306a36Sopenharmony_ci 19862306a36Sopenharmony_ci/* sa11x0_pcmcia_exit() 19962306a36Sopenharmony_ci * ^^^^^^^^^^^^^^^^^^^^ 20062306a36Sopenharmony_ci * Invokes the low-level kernel service to free IRQs associated with this 20162306a36Sopenharmony_ci * socket controller and reset GPIO edge detection. 20262306a36Sopenharmony_ci */ 20362306a36Sopenharmony_cistatic void __exit sa11x0_pcmcia_exit(void) 20462306a36Sopenharmony_ci{ 20562306a36Sopenharmony_ci platform_driver_unregister(&sa11x0_pcmcia_driver); 20662306a36Sopenharmony_ci} 20762306a36Sopenharmony_ci 20862306a36Sopenharmony_ciMODULE_AUTHOR("John Dorsey <john+@cs.cmu.edu>"); 20962306a36Sopenharmony_ciMODULE_DESCRIPTION("Linux PCMCIA Card Services: SA-11x0 Socket Controller"); 21062306a36Sopenharmony_ciMODULE_LICENSE("Dual MPL/GPL"); 21162306a36Sopenharmony_ci 21262306a36Sopenharmony_cifs_initcall(sa11x0_pcmcia_init); 21362306a36Sopenharmony_cimodule_exit(sa11x0_pcmcia_exit); 214