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