18c2ecf20Sopenharmony_ci/*======================================================================
28c2ecf20Sopenharmony_ci
38c2ecf20Sopenharmony_ci    Device driver for the PCMCIA control functionality of StrongARM
48c2ecf20Sopenharmony_ci    SA-1100 microprocessors.
58c2ecf20Sopenharmony_ci
68c2ecf20Sopenharmony_ci    The contents of this file are subject to the Mozilla Public
78c2ecf20Sopenharmony_ci    License Version 1.1 (the "License"); you may not use this file
88c2ecf20Sopenharmony_ci    except in compliance with the License. You may obtain a copy of
98c2ecf20Sopenharmony_ci    the License at http://www.mozilla.org/MPL/
108c2ecf20Sopenharmony_ci
118c2ecf20Sopenharmony_ci    Software distributed under the License is distributed on an "AS
128c2ecf20Sopenharmony_ci    IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
138c2ecf20Sopenharmony_ci    implied. See the License for the specific language governing
148c2ecf20Sopenharmony_ci    rights and limitations under the License.
158c2ecf20Sopenharmony_ci
168c2ecf20Sopenharmony_ci    The initial developer of the original code is John G. Dorsey
178c2ecf20Sopenharmony_ci    <john+@cs.cmu.edu>.  Portions created by John G. Dorsey are
188c2ecf20Sopenharmony_ci    Copyright (C) 1999 John G. Dorsey.  All Rights Reserved.
198c2ecf20Sopenharmony_ci
208c2ecf20Sopenharmony_ci    Alternatively, the contents of this file may be used under the
218c2ecf20Sopenharmony_ci    terms of the GNU Public License version 2 (the "GPL"), in which
228c2ecf20Sopenharmony_ci    case the provisions of the GPL are applicable instead of the
238c2ecf20Sopenharmony_ci    above.  If you wish to allow the use of your version of this file
248c2ecf20Sopenharmony_ci    only under the terms of the GPL and not to allow others to use
258c2ecf20Sopenharmony_ci    your version of this file under the MPL, indicate your decision
268c2ecf20Sopenharmony_ci    by deleting the provisions above and replace them with the notice
278c2ecf20Sopenharmony_ci    and other provisions required by the GPL.  If you do not delete
288c2ecf20Sopenharmony_ci    the provisions above, a recipient may use your version of this
298c2ecf20Sopenharmony_ci    file under either the MPL or the GPL.
308c2ecf20Sopenharmony_ci
318c2ecf20Sopenharmony_ci======================================================================*/
328c2ecf20Sopenharmony_ci
338c2ecf20Sopenharmony_ci#include <linux/module.h>
348c2ecf20Sopenharmony_ci#include <linux/gpio/consumer.h>
358c2ecf20Sopenharmony_ci#include <linux/init.h>
368c2ecf20Sopenharmony_ci#include <linux/regulator/consumer.h>
378c2ecf20Sopenharmony_ci#include <linux/slab.h>
388c2ecf20Sopenharmony_ci#include <linux/platform_device.h>
398c2ecf20Sopenharmony_ci
408c2ecf20Sopenharmony_ci#include <pcmcia/ss.h>
418c2ecf20Sopenharmony_ci
428c2ecf20Sopenharmony_ci#include <asm/hardware/scoop.h>
438c2ecf20Sopenharmony_ci
448c2ecf20Sopenharmony_ci#include "sa1100_generic.h"
458c2ecf20Sopenharmony_ci
468c2ecf20Sopenharmony_cistatic const char *sa11x0_cf_gpio_names[] = {
478c2ecf20Sopenharmony_ci	[SOC_STAT_CD] = "detect",
488c2ecf20Sopenharmony_ci	[SOC_STAT_BVD1] = "bvd1",
498c2ecf20Sopenharmony_ci	[SOC_STAT_BVD2] = "bvd2",
508c2ecf20Sopenharmony_ci	[SOC_STAT_RDY] = "ready",
518c2ecf20Sopenharmony_ci};
528c2ecf20Sopenharmony_ci
538c2ecf20Sopenharmony_cistatic int sa11x0_cf_hw_init(struct soc_pcmcia_socket *skt)
548c2ecf20Sopenharmony_ci{
558c2ecf20Sopenharmony_ci	struct device *dev = skt->socket.dev.parent;
568c2ecf20Sopenharmony_ci	int i;
578c2ecf20Sopenharmony_ci
588c2ecf20Sopenharmony_ci	skt->gpio_reset = devm_gpiod_get(dev, "reset", GPIOD_OUT_HIGH);
598c2ecf20Sopenharmony_ci	if (IS_ERR(skt->gpio_reset))
608c2ecf20Sopenharmony_ci		return PTR_ERR(skt->gpio_reset);
618c2ecf20Sopenharmony_ci
628c2ecf20Sopenharmony_ci	skt->gpio_bus_enable = devm_gpiod_get_optional(dev, "bus-enable",
638c2ecf20Sopenharmony_ci						       GPIOD_OUT_HIGH);
648c2ecf20Sopenharmony_ci	if (IS_ERR(skt->gpio_bus_enable))
658c2ecf20Sopenharmony_ci		return PTR_ERR(skt->gpio_bus_enable);
668c2ecf20Sopenharmony_ci
678c2ecf20Sopenharmony_ci	skt->vcc.reg = devm_regulator_get_optional(dev, "vcc");
688c2ecf20Sopenharmony_ci	if (IS_ERR(skt->vcc.reg))
698c2ecf20Sopenharmony_ci		return PTR_ERR(skt->vcc.reg);
708c2ecf20Sopenharmony_ci
718c2ecf20Sopenharmony_ci	if (!skt->vcc.reg)
728c2ecf20Sopenharmony_ci		dev_warn(dev,
738c2ecf20Sopenharmony_ci			 "no Vcc regulator provided, ignoring Vcc controls\n");
748c2ecf20Sopenharmony_ci
758c2ecf20Sopenharmony_ci	for (i = 0; i < ARRAY_SIZE(sa11x0_cf_gpio_names); i++) {
768c2ecf20Sopenharmony_ci		skt->stat[i].name = sa11x0_cf_gpio_names[i];
778c2ecf20Sopenharmony_ci		skt->stat[i].desc = devm_gpiod_get_optional(dev,
788c2ecf20Sopenharmony_ci					sa11x0_cf_gpio_names[i], GPIOD_IN);
798c2ecf20Sopenharmony_ci		if (IS_ERR(skt->stat[i].desc))
808c2ecf20Sopenharmony_ci			return PTR_ERR(skt->stat[i].desc);
818c2ecf20Sopenharmony_ci	}
828c2ecf20Sopenharmony_ci	return 0;
838c2ecf20Sopenharmony_ci}
848c2ecf20Sopenharmony_ci
858c2ecf20Sopenharmony_cistatic int sa11x0_cf_configure_socket(struct soc_pcmcia_socket *skt,
868c2ecf20Sopenharmony_ci	const socket_state_t *state)
878c2ecf20Sopenharmony_ci{
888c2ecf20Sopenharmony_ci	return soc_pcmcia_regulator_set(skt, &skt->vcc, state->Vcc);
898c2ecf20Sopenharmony_ci}
908c2ecf20Sopenharmony_ci
918c2ecf20Sopenharmony_cistatic struct pcmcia_low_level sa11x0_cf_ops = {
928c2ecf20Sopenharmony_ci	.owner = THIS_MODULE,
938c2ecf20Sopenharmony_ci	.hw_init = sa11x0_cf_hw_init,
948c2ecf20Sopenharmony_ci	.socket_state = soc_common_cf_socket_state,
958c2ecf20Sopenharmony_ci	.configure_socket = sa11x0_cf_configure_socket,
968c2ecf20Sopenharmony_ci};
978c2ecf20Sopenharmony_ci
988c2ecf20Sopenharmony_ciint __init pcmcia_collie_init(struct device *dev);
998c2ecf20Sopenharmony_ci
1008c2ecf20Sopenharmony_cistatic int (*sa11x0_pcmcia_legacy_hw_init[])(struct device *dev) = {
1018c2ecf20Sopenharmony_ci#if defined(CONFIG_SA1100_H3100) || defined(CONFIG_SA1100_H3600)
1028c2ecf20Sopenharmony_ci	pcmcia_h3600_init,
1038c2ecf20Sopenharmony_ci#endif
1048c2ecf20Sopenharmony_ci#ifdef CONFIG_SA1100_SIMPAD
1058c2ecf20Sopenharmony_ci	pcmcia_simpad_init,
1068c2ecf20Sopenharmony_ci#endif
1078c2ecf20Sopenharmony_ci#ifdef CONFIG_SA1100_COLLIE
1088c2ecf20Sopenharmony_ci       pcmcia_collie_init,
1098c2ecf20Sopenharmony_ci#endif
1108c2ecf20Sopenharmony_ci};
1118c2ecf20Sopenharmony_ci
1128c2ecf20Sopenharmony_cistatic int sa11x0_drv_pcmcia_legacy_probe(struct platform_device *dev)
1138c2ecf20Sopenharmony_ci{
1148c2ecf20Sopenharmony_ci	int i, ret = -ENODEV;
1158c2ecf20Sopenharmony_ci
1168c2ecf20Sopenharmony_ci	/*
1178c2ecf20Sopenharmony_ci	 * Initialise any "on-board" PCMCIA sockets.
1188c2ecf20Sopenharmony_ci	 */
1198c2ecf20Sopenharmony_ci	for (i = 0; i < ARRAY_SIZE(sa11x0_pcmcia_legacy_hw_init); i++) {
1208c2ecf20Sopenharmony_ci		ret = sa11x0_pcmcia_legacy_hw_init[i](&dev->dev);
1218c2ecf20Sopenharmony_ci		if (ret == 0)
1228c2ecf20Sopenharmony_ci			break;
1238c2ecf20Sopenharmony_ci	}
1248c2ecf20Sopenharmony_ci
1258c2ecf20Sopenharmony_ci	return ret;
1268c2ecf20Sopenharmony_ci}
1278c2ecf20Sopenharmony_ci
1288c2ecf20Sopenharmony_cistatic int sa11x0_drv_pcmcia_legacy_remove(struct platform_device *dev)
1298c2ecf20Sopenharmony_ci{
1308c2ecf20Sopenharmony_ci	struct skt_dev_info *sinfo = platform_get_drvdata(dev);
1318c2ecf20Sopenharmony_ci	int i;
1328c2ecf20Sopenharmony_ci
1338c2ecf20Sopenharmony_ci	platform_set_drvdata(dev, NULL);
1348c2ecf20Sopenharmony_ci
1358c2ecf20Sopenharmony_ci	for (i = 0; i < sinfo->nskt; i++)
1368c2ecf20Sopenharmony_ci		soc_pcmcia_remove_one(&sinfo->skt[i]);
1378c2ecf20Sopenharmony_ci
1388c2ecf20Sopenharmony_ci	return 0;
1398c2ecf20Sopenharmony_ci}
1408c2ecf20Sopenharmony_ci
1418c2ecf20Sopenharmony_cistatic int sa11x0_drv_pcmcia_probe(struct platform_device *pdev)
1428c2ecf20Sopenharmony_ci{
1438c2ecf20Sopenharmony_ci	struct soc_pcmcia_socket *skt;
1448c2ecf20Sopenharmony_ci	struct device *dev = &pdev->dev;
1458c2ecf20Sopenharmony_ci
1468c2ecf20Sopenharmony_ci	if (pdev->id == -1)
1478c2ecf20Sopenharmony_ci		return sa11x0_drv_pcmcia_legacy_probe(pdev);
1488c2ecf20Sopenharmony_ci
1498c2ecf20Sopenharmony_ci	skt = devm_kzalloc(dev, sizeof(*skt), GFP_KERNEL);
1508c2ecf20Sopenharmony_ci	if (!skt)
1518c2ecf20Sopenharmony_ci		return -ENOMEM;
1528c2ecf20Sopenharmony_ci
1538c2ecf20Sopenharmony_ci	platform_set_drvdata(pdev, skt);
1548c2ecf20Sopenharmony_ci
1558c2ecf20Sopenharmony_ci	skt->nr = pdev->id;
1568c2ecf20Sopenharmony_ci	skt->clk = devm_clk_get(dev, NULL);
1578c2ecf20Sopenharmony_ci	if (IS_ERR(skt->clk))
1588c2ecf20Sopenharmony_ci		return PTR_ERR(skt->clk);
1598c2ecf20Sopenharmony_ci
1608c2ecf20Sopenharmony_ci	sa11xx_drv_pcmcia_ops(&sa11x0_cf_ops);
1618c2ecf20Sopenharmony_ci	soc_pcmcia_init_one(skt, &sa11x0_cf_ops, dev);
1628c2ecf20Sopenharmony_ci
1638c2ecf20Sopenharmony_ci	return sa11xx_drv_pcmcia_add_one(skt);
1648c2ecf20Sopenharmony_ci}
1658c2ecf20Sopenharmony_ci
1668c2ecf20Sopenharmony_cistatic int sa11x0_drv_pcmcia_remove(struct platform_device *dev)
1678c2ecf20Sopenharmony_ci{
1688c2ecf20Sopenharmony_ci	struct soc_pcmcia_socket *skt;
1698c2ecf20Sopenharmony_ci
1708c2ecf20Sopenharmony_ci	if (dev->id == -1)
1718c2ecf20Sopenharmony_ci		return sa11x0_drv_pcmcia_legacy_remove(dev);
1728c2ecf20Sopenharmony_ci
1738c2ecf20Sopenharmony_ci	skt = platform_get_drvdata(dev);
1748c2ecf20Sopenharmony_ci
1758c2ecf20Sopenharmony_ci	soc_pcmcia_remove_one(skt);
1768c2ecf20Sopenharmony_ci
1778c2ecf20Sopenharmony_ci	return 0;
1788c2ecf20Sopenharmony_ci}
1798c2ecf20Sopenharmony_ci
1808c2ecf20Sopenharmony_cistatic struct platform_driver sa11x0_pcmcia_driver = {
1818c2ecf20Sopenharmony_ci	.driver = {
1828c2ecf20Sopenharmony_ci		.name		= "sa11x0-pcmcia",
1838c2ecf20Sopenharmony_ci	},
1848c2ecf20Sopenharmony_ci	.probe		= sa11x0_drv_pcmcia_probe,
1858c2ecf20Sopenharmony_ci	.remove		= sa11x0_drv_pcmcia_remove,
1868c2ecf20Sopenharmony_ci};
1878c2ecf20Sopenharmony_ci
1888c2ecf20Sopenharmony_ci/* sa11x0_pcmcia_init()
1898c2ecf20Sopenharmony_ci * ^^^^^^^^^^^^^^^^^^^^
1908c2ecf20Sopenharmony_ci *
1918c2ecf20Sopenharmony_ci * This routine performs low-level PCMCIA initialization and then
1928c2ecf20Sopenharmony_ci * registers this socket driver with Card Services.
1938c2ecf20Sopenharmony_ci *
1948c2ecf20Sopenharmony_ci * Returns: 0 on success, -ve error code on failure
1958c2ecf20Sopenharmony_ci */
1968c2ecf20Sopenharmony_cistatic int __init sa11x0_pcmcia_init(void)
1978c2ecf20Sopenharmony_ci{
1988c2ecf20Sopenharmony_ci	return platform_driver_register(&sa11x0_pcmcia_driver);
1998c2ecf20Sopenharmony_ci}
2008c2ecf20Sopenharmony_ci
2018c2ecf20Sopenharmony_ci/* sa11x0_pcmcia_exit()
2028c2ecf20Sopenharmony_ci * ^^^^^^^^^^^^^^^^^^^^
2038c2ecf20Sopenharmony_ci * Invokes the low-level kernel service to free IRQs associated with this
2048c2ecf20Sopenharmony_ci * socket controller and reset GPIO edge detection.
2058c2ecf20Sopenharmony_ci */
2068c2ecf20Sopenharmony_cistatic void __exit sa11x0_pcmcia_exit(void)
2078c2ecf20Sopenharmony_ci{
2088c2ecf20Sopenharmony_ci	platform_driver_unregister(&sa11x0_pcmcia_driver);
2098c2ecf20Sopenharmony_ci}
2108c2ecf20Sopenharmony_ci
2118c2ecf20Sopenharmony_ciMODULE_AUTHOR("John Dorsey <john+@cs.cmu.edu>");
2128c2ecf20Sopenharmony_ciMODULE_DESCRIPTION("Linux PCMCIA Card Services: SA-11x0 Socket Controller");
2138c2ecf20Sopenharmony_ciMODULE_LICENSE("Dual MPL/GPL");
2148c2ecf20Sopenharmony_ci
2158c2ecf20Sopenharmony_cifs_initcall(sa11x0_pcmcia_init);
2168c2ecf20Sopenharmony_cimodule_exit(sa11x0_pcmcia_exit);
217