mirror of https://github.com/Qortal/Brooklyn
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
327 lines
6.1 KiB
327 lines
6.1 KiB
// SPDX-License-Identifier: GPL-2.0-only |
|
/* |
|
* Copyright (C) 2006-2007 PA Semi, Inc |
|
* |
|
* Author: Olof Johansson, PA Semi |
|
* |
|
* Maintained by: Olof Johansson <[email protected]> |
|
* |
|
* Based on drivers/net/fs_enet/mii-bitbang.c. |
|
*/ |
|
|
|
#include <linux/io.h> |
|
#include <linux/module.h> |
|
#include <linux/types.h> |
|
#include <linux/slab.h> |
|
#include <linux/sched.h> |
|
#include <linux/errno.h> |
|
#include <linux/ioport.h> |
|
#include <linux/interrupt.h> |
|
#include <linux/phy.h> |
|
#include <linux/of_address.h> |
|
#include <linux/of_mdio.h> |
|
#include <linux/of_platform.h> |
|
|
|
#define DELAY 1 |
|
|
|
static void __iomem *gpio_regs; |
|
|
|
struct gpio_priv { |
|
int mdc_pin; |
|
int mdio_pin; |
|
}; |
|
|
|
#define MDC_PIN(bus) (((struct gpio_priv *)bus->priv)->mdc_pin) |
|
#define MDIO_PIN(bus) (((struct gpio_priv *)bus->priv)->mdio_pin) |
|
|
|
static inline void mdio_lo(struct mii_bus *bus) |
|
{ |
|
out_le32(gpio_regs+0x10, 1 << MDIO_PIN(bus)); |
|
} |
|
|
|
static inline void mdio_hi(struct mii_bus *bus) |
|
{ |
|
out_le32(gpio_regs, 1 << MDIO_PIN(bus)); |
|
} |
|
|
|
static inline void mdc_lo(struct mii_bus *bus) |
|
{ |
|
out_le32(gpio_regs+0x10, 1 << MDC_PIN(bus)); |
|
} |
|
|
|
static inline void mdc_hi(struct mii_bus *bus) |
|
{ |
|
out_le32(gpio_regs, 1 << MDC_PIN(bus)); |
|
} |
|
|
|
static inline void mdio_active(struct mii_bus *bus) |
|
{ |
|
out_le32(gpio_regs+0x20, (1 << MDC_PIN(bus)) | (1 << MDIO_PIN(bus))); |
|
} |
|
|
|
static inline void mdio_tristate(struct mii_bus *bus) |
|
{ |
|
out_le32(gpio_regs+0x30, (1 << MDIO_PIN(bus))); |
|
} |
|
|
|
static inline int mdio_read(struct mii_bus *bus) |
|
{ |
|
return !!(in_le32(gpio_regs+0x40) & (1 << MDIO_PIN(bus))); |
|
} |
|
|
|
static void clock_out(struct mii_bus *bus, int bit) |
|
{ |
|
if (bit) |
|
mdio_hi(bus); |
|
else |
|
mdio_lo(bus); |
|
udelay(DELAY); |
|
mdc_hi(bus); |
|
udelay(DELAY); |
|
mdc_lo(bus); |
|
} |
|
|
|
/* Utility to send the preamble, address, and register (common to read and write). */ |
|
static void bitbang_pre(struct mii_bus *bus, int read, u8 addr, u8 reg) |
|
{ |
|
int i; |
|
|
|
/* CFE uses a really long preamble (40 bits). We'll do the same. */ |
|
mdio_active(bus); |
|
for (i = 0; i < 40; i++) { |
|
clock_out(bus, 1); |
|
} |
|
|
|
/* send the start bit (01) and the read opcode (10) or write (10) */ |
|
clock_out(bus, 0); |
|
clock_out(bus, 1); |
|
|
|
clock_out(bus, read); |
|
clock_out(bus, !read); |
|
|
|
/* send the PHY address */ |
|
for (i = 0; i < 5; i++) { |
|
clock_out(bus, (addr & 0x10) != 0); |
|
addr <<= 1; |
|
} |
|
|
|
/* send the register address */ |
|
for (i = 0; i < 5; i++) { |
|
clock_out(bus, (reg & 0x10) != 0); |
|
reg <<= 1; |
|
} |
|
} |
|
|
|
static int gpio_mdio_read(struct mii_bus *bus, int phy_id, int location) |
|
{ |
|
u16 rdreg; |
|
int ret, i; |
|
u8 addr = phy_id & 0xff; |
|
u8 reg = location & 0xff; |
|
|
|
bitbang_pre(bus, 1, addr, reg); |
|
|
|
/* tri-state our MDIO I/O pin so we can read */ |
|
mdio_tristate(bus); |
|
udelay(DELAY); |
|
mdc_hi(bus); |
|
udelay(DELAY); |
|
mdc_lo(bus); |
|
|
|
/* read 16 bits of register data, MSB first */ |
|
rdreg = 0; |
|
for (i = 0; i < 16; i++) { |
|
mdc_lo(bus); |
|
udelay(DELAY); |
|
mdc_hi(bus); |
|
udelay(DELAY); |
|
mdc_lo(bus); |
|
udelay(DELAY); |
|
rdreg <<= 1; |
|
rdreg |= mdio_read(bus); |
|
} |
|
|
|
mdc_hi(bus); |
|
udelay(DELAY); |
|
mdc_lo(bus); |
|
udelay(DELAY); |
|
|
|
ret = rdreg; |
|
|
|
return ret; |
|
} |
|
|
|
static int gpio_mdio_write(struct mii_bus *bus, int phy_id, int location, u16 val) |
|
{ |
|
int i; |
|
|
|
u8 addr = phy_id & 0xff; |
|
u8 reg = location & 0xff; |
|
u16 value = val & 0xffff; |
|
|
|
bitbang_pre(bus, 0, addr, reg); |
|
|
|
/* send the turnaround (10) */ |
|
mdc_lo(bus); |
|
mdio_hi(bus); |
|
udelay(DELAY); |
|
mdc_hi(bus); |
|
udelay(DELAY); |
|
mdc_lo(bus); |
|
mdio_lo(bus); |
|
udelay(DELAY); |
|
mdc_hi(bus); |
|
udelay(DELAY); |
|
|
|
/* write 16 bits of register data, MSB first */ |
|
for (i = 0; i < 16; i++) { |
|
mdc_lo(bus); |
|
if (value & 0x8000) |
|
mdio_hi(bus); |
|
else |
|
mdio_lo(bus); |
|
udelay(DELAY); |
|
mdc_hi(bus); |
|
udelay(DELAY); |
|
value <<= 1; |
|
} |
|
|
|
/* |
|
* Tri-state the MDIO line. |
|
*/ |
|
mdio_tristate(bus); |
|
mdc_lo(bus); |
|
udelay(DELAY); |
|
mdc_hi(bus); |
|
udelay(DELAY); |
|
return 0; |
|
} |
|
|
|
static int gpio_mdio_reset(struct mii_bus *bus) |
|
{ |
|
/*nothing here - dunno how to reset it*/ |
|
return 0; |
|
} |
|
|
|
|
|
static int gpio_mdio_probe(struct platform_device *ofdev) |
|
{ |
|
struct device *dev = &ofdev->dev; |
|
struct device_node *np = ofdev->dev.of_node; |
|
struct mii_bus *new_bus; |
|
struct gpio_priv *priv; |
|
const unsigned int *prop; |
|
int err; |
|
|
|
err = -ENOMEM; |
|
priv = kzalloc(sizeof(struct gpio_priv), GFP_KERNEL); |
|
if (!priv) |
|
goto out; |
|
|
|
new_bus = mdiobus_alloc(); |
|
|
|
if (!new_bus) |
|
goto out_free_priv; |
|
|
|
new_bus->name = "pasemi gpio mdio bus"; |
|
new_bus->read = &gpio_mdio_read; |
|
new_bus->write = &gpio_mdio_write; |
|
new_bus->reset = &gpio_mdio_reset; |
|
|
|
prop = of_get_property(np, "reg", NULL); |
|
snprintf(new_bus->id, MII_BUS_ID_SIZE, "%x", *prop); |
|
new_bus->priv = priv; |
|
|
|
prop = of_get_property(np, "mdc-pin", NULL); |
|
priv->mdc_pin = *prop; |
|
|
|
prop = of_get_property(np, "mdio-pin", NULL); |
|
priv->mdio_pin = *prop; |
|
|
|
new_bus->parent = dev; |
|
dev_set_drvdata(dev, new_bus); |
|
|
|
err = of_mdiobus_register(new_bus, np); |
|
|
|
if (err != 0) { |
|
pr_err("%s: Cannot register as MDIO bus, err %d\n", |
|
new_bus->name, err); |
|
goto out_free_irq; |
|
} |
|
|
|
return 0; |
|
|
|
out_free_irq: |
|
kfree(new_bus); |
|
out_free_priv: |
|
kfree(priv); |
|
out: |
|
return err; |
|
} |
|
|
|
|
|
static int gpio_mdio_remove(struct platform_device *dev) |
|
{ |
|
struct mii_bus *bus = dev_get_drvdata(&dev->dev); |
|
|
|
mdiobus_unregister(bus); |
|
|
|
dev_set_drvdata(&dev->dev, NULL); |
|
|
|
kfree(bus->priv); |
|
bus->priv = NULL; |
|
mdiobus_free(bus); |
|
|
|
return 0; |
|
} |
|
|
|
static const struct of_device_id gpio_mdio_match[] = |
|
{ |
|
{ |
|
.compatible = "gpio-mdio", |
|
}, |
|
{}, |
|
}; |
|
MODULE_DEVICE_TABLE(of, gpio_mdio_match); |
|
|
|
static struct platform_driver gpio_mdio_driver = |
|
{ |
|
.probe = gpio_mdio_probe, |
|
.remove = gpio_mdio_remove, |
|
.driver = { |
|
.name = "gpio-mdio-bitbang", |
|
.of_match_table = gpio_mdio_match, |
|
}, |
|
}; |
|
|
|
static int gpio_mdio_init(void) |
|
{ |
|
struct device_node *np; |
|
|
|
np = of_find_compatible_node(NULL, NULL, "1682m-gpio"); |
|
if (!np) |
|
np = of_find_compatible_node(NULL, NULL, |
|
"pasemi,pwrficient-gpio"); |
|
if (!np) |
|
return -ENODEV; |
|
gpio_regs = of_iomap(np, 0); |
|
of_node_put(np); |
|
|
|
if (!gpio_regs) |
|
return -ENODEV; |
|
|
|
return platform_driver_register(&gpio_mdio_driver); |
|
} |
|
module_init(gpio_mdio_init); |
|
|
|
static void gpio_mdio_exit(void) |
|
{ |
|
platform_driver_unregister(&gpio_mdio_driver); |
|
if (gpio_regs) |
|
iounmap(gpio_regs); |
|
} |
|
module_exit(gpio_mdio_exit); |
|
|
|
MODULE_LICENSE("GPL"); |
|
MODULE_AUTHOR("Olof Johansson <[email protected]>"); |
|
MODULE_DESCRIPTION("Driver for MDIO over GPIO on PA Semi PWRficient-based boards");
|
|
|