| /* | 
 |  * OpenFirmware GPIO based MDIO bitbang driver. | 
 |  * | 
 |  * Copyright (c) 2008 CSE Semaphore Belgium. | 
 |  *  by Laurent Pinchart <laurentp@cse-semaphore.com> | 
 |  * | 
 |  * Based on earlier work by | 
 |  * | 
 |  * Copyright (c) 2003 Intracom S.A. | 
 |  *  by Pantelis Antoniou <panto@intracom.gr> | 
 |  * | 
 |  * 2005 (c) MontaVista Software, Inc. | 
 |  * Vitaly Bordug <vbordug@ru.mvista.com> | 
 |  * | 
 |  * This file is licensed under the terms of the GNU General Public License | 
 |  * version 2. This program is licensed "as is" without any warranty of any | 
 |  * kind, whether express or implied. | 
 |  */ | 
 |  | 
 | #include <linux/module.h> | 
 | #include <linux/slab.h> | 
 | #include <linux/init.h> | 
 | #include <linux/interrupt.h> | 
 | #include <linux/mdio-bitbang.h> | 
 | #include <linux/of_gpio.h> | 
 | #include <linux/of_platform.h> | 
 |  | 
 | struct mdio_gpio_info { | 
 | 	struct mdiobb_ctrl ctrl; | 
 | 	int mdc, mdio; | 
 | }; | 
 |  | 
 | static void mdio_dir(struct mdiobb_ctrl *ctrl, int dir) | 
 | { | 
 | 	struct mdio_gpio_info *bitbang = | 
 | 		container_of(ctrl, struct mdio_gpio_info, ctrl); | 
 |  | 
 | 	if (dir) | 
 | 		gpio_direction_output(bitbang->mdio, 1); | 
 | 	else | 
 | 		gpio_direction_input(bitbang->mdio); | 
 | } | 
 |  | 
 | static int mdio_read(struct mdiobb_ctrl *ctrl) | 
 | { | 
 | 	struct mdio_gpio_info *bitbang = | 
 | 		container_of(ctrl, struct mdio_gpio_info, ctrl); | 
 |  | 
 | 	return gpio_get_value(bitbang->mdio); | 
 | } | 
 |  | 
 | static void mdio(struct mdiobb_ctrl *ctrl, int what) | 
 | { | 
 | 	struct mdio_gpio_info *bitbang = | 
 | 		container_of(ctrl, struct mdio_gpio_info, ctrl); | 
 |  | 
 | 	gpio_set_value(bitbang->mdio, what); | 
 | } | 
 |  | 
 | static void mdc(struct mdiobb_ctrl *ctrl, int what) | 
 | { | 
 | 	struct mdio_gpio_info *bitbang = | 
 | 		container_of(ctrl, struct mdio_gpio_info, ctrl); | 
 |  | 
 | 	gpio_set_value(bitbang->mdc, what); | 
 | } | 
 |  | 
 | static struct mdiobb_ops mdio_gpio_ops = { | 
 | 	.owner = THIS_MODULE, | 
 | 	.set_mdc = mdc, | 
 | 	.set_mdio_dir = mdio_dir, | 
 | 	.set_mdio_data = mdio, | 
 | 	.get_mdio_data = mdio_read, | 
 | }; | 
 |  | 
 | static int __devinit mdio_ofgpio_bitbang_init(struct mii_bus *bus, | 
 |                                          struct device_node *np) | 
 | { | 
 | 	struct mdio_gpio_info *bitbang = bus->priv; | 
 |  | 
 | 	bitbang->mdc = of_get_gpio(np, 0); | 
 | 	bitbang->mdio = of_get_gpio(np, 1); | 
 |  | 
 | 	if (bitbang->mdc < 0 || bitbang->mdio < 0) | 
 | 		return -ENODEV; | 
 |  | 
 | 	snprintf(bus->id, MII_BUS_ID_SIZE, "%x", bitbang->mdc); | 
 | 	return 0; | 
 | } | 
 |  | 
 | static void __devinit add_phy(struct mii_bus *bus, struct device_node *np) | 
 | { | 
 | 	const u32 *data; | 
 | 	int len, id, irq; | 
 |  | 
 | 	data = of_get_property(np, "reg", &len); | 
 | 	if (!data || len != 4) | 
 | 		return; | 
 |  | 
 | 	id = *data; | 
 | 	bus->phy_mask &= ~(1 << id); | 
 |  | 
 | 	irq = of_irq_to_resource(np, 0, NULL); | 
 | 	if (irq != NO_IRQ) | 
 | 		bus->irq[id] = irq; | 
 | } | 
 |  | 
 | static int __devinit mdio_ofgpio_probe(struct of_device *ofdev, | 
 |                                         const struct of_device_id *match) | 
 | { | 
 | 	struct device_node *np = NULL; | 
 | 	struct mii_bus *new_bus; | 
 | 	struct mdio_gpio_info *bitbang; | 
 | 	int ret = -ENOMEM; | 
 | 	int i; | 
 |  | 
 | 	bitbang = kzalloc(sizeof(struct mdio_gpio_info), GFP_KERNEL); | 
 | 	if (!bitbang) | 
 | 		goto out; | 
 |  | 
 | 	bitbang->ctrl.ops = &mdio_gpio_ops; | 
 |  | 
 | 	new_bus = alloc_mdio_bitbang(&bitbang->ctrl); | 
 | 	if (!new_bus) | 
 | 		goto out_free_priv; | 
 |  | 
 | 	new_bus->name = "GPIO Bitbanged MII", | 
 |  | 
 | 	ret = mdio_ofgpio_bitbang_init(new_bus, ofdev->node); | 
 | 	if (ret) | 
 | 		goto out_free_bus; | 
 |  | 
 | 	new_bus->phy_mask = ~0; | 
 | 	new_bus->irq = kmalloc(sizeof(int) * PHY_MAX_ADDR, GFP_KERNEL); | 
 | 	if (!new_bus->irq) | 
 | 		goto out_free_bus; | 
 |  | 
 | 	for (i = 0; i < PHY_MAX_ADDR; i++) | 
 | 		new_bus->irq[i] = -1; | 
 |  | 
 | 	while ((np = of_get_next_child(ofdev->node, np))) | 
 | 		if (!strcmp(np->type, "ethernet-phy")) | 
 | 			add_phy(new_bus, np); | 
 |  | 
 | 	new_bus->dev = &ofdev->dev; | 
 | 	dev_set_drvdata(&ofdev->dev, new_bus); | 
 |  | 
 | 	ret = mdiobus_register(new_bus); | 
 | 	if (ret) | 
 | 		goto out_free_irqs; | 
 |  | 
 | 	return 0; | 
 |  | 
 | out_free_irqs: | 
 | 	dev_set_drvdata(&ofdev->dev, NULL); | 
 | 	kfree(new_bus->irq); | 
 | out_free_bus: | 
 | 	kfree(new_bus); | 
 | out_free_priv: | 
 | 	free_mdio_bitbang(new_bus); | 
 | out: | 
 | 	return ret; | 
 | } | 
 |  | 
 | static int mdio_ofgpio_remove(struct of_device *ofdev) | 
 | { | 
 | 	struct mii_bus *bus = dev_get_drvdata(&ofdev->dev); | 
 | 	struct mdio_gpio_info *bitbang = bus->priv; | 
 |  | 
 | 	mdiobus_unregister(bus); | 
 | 	free_mdio_bitbang(bus); | 
 | 	dev_set_drvdata(&ofdev->dev, NULL); | 
 | 	kfree(bus->irq); | 
 | 	kfree(bitbang); | 
 | 	kfree(bus); | 
 |  | 
 | 	return 0; | 
 | } | 
 |  | 
 | static struct of_device_id mdio_ofgpio_match[] = { | 
 | 	{ | 
 | 		.compatible = "virtual,mdio-gpio", | 
 | 	}, | 
 | 	{}, | 
 | }; | 
 |  | 
 | static struct of_platform_driver mdio_ofgpio_driver = { | 
 | 	.name = "mdio-gpio", | 
 | 	.match_table = mdio_ofgpio_match, | 
 | 	.probe = mdio_ofgpio_probe, | 
 | 	.remove = mdio_ofgpio_remove, | 
 | }; | 
 |  | 
 | static int mdio_ofgpio_init(void) | 
 | { | 
 | 	return of_register_platform_driver(&mdio_ofgpio_driver); | 
 | } | 
 |  | 
 | static void mdio_ofgpio_exit(void) | 
 | { | 
 | 	of_unregister_platform_driver(&mdio_ofgpio_driver); | 
 | } | 
 |  | 
 | module_init(mdio_ofgpio_init); | 
 | module_exit(mdio_ofgpio_exit); |