MII 接口解析(三)GPIO 模拟 MDIO 接口使用代码

2020-09-23  本文已影响0人  Nothing_655f

GPIO 模拟 MDIO 接口使用代码
如果要通过GPIO来模拟MDIO通信,那么我们就是通过模拟其帧格式的时序来通信,

前面介绍过一遍其帧格式了,MDIO上数据帧的格式如下:

mdio frame

内核中提供了一个mdio-bitbang.c,里面实现了一套软件实现的MDIO/MDC接口时序可供参考。

数据结构

struct mdiobb_ops {
    struct module *owner;
    /* Set the Management Data Clock high if level is one,
     * low if level is zero.
     */
    void (*set_mdc)(struct mdiobb_ctrl *ctrl, int level);
    /* Configure the Management Data I/O pin as an input if
     * "output" is zero, or an output if "output" is one.
     */
    void (*set_mdio_dir)(struct mdiobb_ctrl *ctrl, int output);
    /* Set the Management Data I/O pin high if value is one,
     * low if "value" is zero.  This may only be called
     * when the MDIO pin is configured as an output.
     */
    void (*set_mdio_data)(struct mdiobb_ctrl *ctrl, int value);
    /* Retrieve the state Management Data I/O pin. */
    int (*get_mdio_data)(struct mdiobb_ctrl *ctrl);
};
struct mdiobb_ctrl {
    const struct mdiobb_ops *ops;
};

set_mdc,发送MDC 信号的函数,由具体使用情况来决定实际的mdc 信号是如何发送
set_mdio_dir,设置MDIO方向的函数,由具体使用情况来决定实际的mdc 信号是如何发送
set_mdio_data,发送MDIO数据到MMD,由具体使用情况来决定实际的mdc 信号是如何发送
get_mdio_data,从MMD接收MDIO数据,由具体使用情况来决定实际的mdc 信号是如何发送
使用方法
通过调用 alloc_mdio_bitbang 来获得一个mii_bus, 并且注册read/wirte 方法为 mdiobb_read/mdiobb_write,并且将mdiobb_ctrl 作为mii bus的私有成员来操作器后续的ops 函数

struct mii_bus *alloc_mdio_bitbang(struct mdiobb_ctrl *ctrl)
{
    struct mii_bus *bus;
    bus = mdiobus_alloc();
    if (!bus)
        return NULL;
    __module_get(ctrl->ops->owner);
    bus->read = mdiobb_read;
    bus->write = mdiobb_write;
    bus->priv = ctrl;
    return bus;
}

以wirte 为例,bitbang中帮我们封装好了对应的时序,我们需要做的就是定义好前面注册的 ops mdc mdio操作

static int mdiobb_write(struct mii_bus *bus, int phy, int reg, u16 val)
{
    struct mdiobb_ctrl *ctrl = bus->priv;
    if (reg & MII_ADDR_C45) {
        reg = mdiobb_cmd_addr(ctrl, phy, reg);
        mdiobb_cmd(ctrl, MDIO_C45_WRITE, phy, reg);
    } else
        mdiobb_cmd(ctrl, MDIO_WRITE, phy, reg);
    /* send the turnaround (10) */
    mdiobb_send_bit(ctrl, 1);
    mdiobb_send_bit(ctrl, 0);
    mdiobb_send_num(ctrl, val, 16);
    ctrl->ops->set_mdio_dir(ctrl, 0);
    mdiobb_get_bit(ctrl);
    return 0;
}

内核中也有一个GPIOI-MDIO的例子,可以参考其来移植到自己的驱动中

#include <linux/module.h>
#include <linux/slab.h>
#include <linux/interrupt.h>
#include <linux/platform_device.h>
#include <linux/platform_data/mdio-gpio.h>
#include <linux/mdio-bitbang.h>
#include <linux/mdio-gpio.h>
#include <linux/gpio/consumer.h>
#include <linux/of_mdio.h>
struct mdio_gpio_info {
    struct mdiobb_ctrl ctrl;
    struct gpio_desc *mdc, *mdio, *mdo;
};
static int mdio_gpio_get_data(struct device *dev,
                  struct mdio_gpio_info *bitbang)
{
    bitbang->mdc = devm_gpiod_get_index(dev, NULL, MDIO_GPIO_MDC,
                        GPIOD_OUT_LOW);
    if (IS_ERR(bitbang->mdc))
        return PTR_ERR(bitbang->mdc);
    bitbang->mdio = devm_gpiod_get_index(dev, NULL, MDIO_GPIO_MDIO,
                         GPIOD_IN);
    if (IS_ERR(bitbang->mdio))
        return PTR_ERR(bitbang->mdio);
    bitbang->mdo = devm_gpiod_get_index_optional(dev, NULL, MDIO_GPIO_MDO,
                             GPIOD_OUT_LOW);
    return PTR_ERR_OR_ZERO(bitbang->mdo);
}
static void mdio_dir(struct mdiobb_ctrl *ctrl, int dir)
{
    struct mdio_gpio_info *bitbang =
        container_of(ctrl, struct mdio_gpio_info, ctrl);
    if (bitbang->mdo) {
        /* Separate output pin. Always set its value to high
         * when changing direction. If direction is input,
         * assume the pin serves as pull-up. If direction is
         * output, the default value is high.
         */
        gpiod_set_value_cansleep(bitbang->mdo, 1);
        return;
    }
    if (dir)
        gpiod_direction_output(bitbang->mdio, 1);
    else
        gpiod_direction_input(bitbang->mdio);
}
static int mdio_get(struct mdiobb_ctrl *ctrl)
{
    struct mdio_gpio_info *bitbang =
        container_of(ctrl, struct mdio_gpio_info, ctrl);
    return gpiod_get_value_cansleep(bitbang->mdio);
}
static void mdio_set(struct mdiobb_ctrl *ctrl, int what)
{
    struct mdio_gpio_info *bitbang =
        container_of(ctrl, struct mdio_gpio_info, ctrl);
    if (bitbang->mdo)
        gpiod_set_value_cansleep(bitbang->mdo, what);
    else
        gpiod_set_value_cansleep(bitbang->mdio, what);
}
static void mdc_set(struct mdiobb_ctrl *ctrl, int what)
{
    struct mdio_gpio_info *bitbang =
        container_of(ctrl, struct mdio_gpio_info, ctrl);
    gpiod_set_value_cansleep(bitbang->mdc, what);
}
static const struct mdiobb_ops mdio_gpio_ops = {
    .owner = THIS_MODULE,
    .set_mdc = mdc_set,
    .set_mdio_dir = mdio_dir,
    .set_mdio_data = mdio_set,
    .get_mdio_data = mdio_get,
};
static struct mii_bus *mdio_gpio_bus_init(struct device *dev,
                      struct mdio_gpio_info *bitbang,
                      int bus_id)
{
    struct mdio_gpio_platform_data *pdata = dev_get_platdata(dev);
    struct mii_bus *new_bus;
    bitbang->ctrl.ops = &mdio_gpio_ops;
    new_bus = alloc_mdio_bitbang(&bitbang->ctrl);
    if (!new_bus)
        return NULL;
    new_bus->name = "GPIO Bitbanged MDIO";
    new_bus->parent = dev;
    if (bus_id != -1)
        snprintf(new_bus->id, MII_BUS_ID_SIZE, "gpio-%x", bus_id);
    else
        strncpy(new_bus->id, "gpio", MII_BUS_ID_SIZE);
    if (pdata) {
        new_bus->phy_mask = pdata->phy_mask;
        new_bus->phy_ignore_ta_mask = pdata->phy_ignore_ta_mask;
    }
    dev_set_drvdata(dev, new_bus);
    return new_bus;
}
static void mdio_gpio_bus_deinit(struct device *dev)
{
    struct mii_bus *bus = dev_get_drvdata(dev);
    free_mdio_bitbang(bus);
}
static void mdio_gpio_bus_destroy(struct device *dev)
{
    struct mii_bus *bus = dev_get_drvdata(dev);
    mdiobus_unregister(bus);
    mdio_gpio_bus_deinit(dev);
}
static int mdio_gpio_probe(struct platform_device *pdev)
{
    struct mdio_gpio_info *bitbang;
    struct mii_bus *new_bus;
    int ret, bus_id;
    bitbang = devm_kzalloc(&pdev->dev, sizeof(*bitbang), GFP_KERNEL);
    if (!bitbang)
        return -ENOMEM;
    ret = mdio_gpio_get_data(&pdev->dev, bitbang);
    if (ret)
        return ret;
    if (pdev->dev.of_node) {
        bus_id = of_alias_get_id(pdev->dev.of_node, "mdio-gpio");
        if (bus_id < 0) {
            dev_warn(&pdev->dev, "failed to get alias id\n");
            bus_id = 0;
        }
    } else {
        bus_id = pdev->id;
    }
    new_bus = mdio_gpio_bus_init(&pdev->dev, bitbang, bus_id);
    if (!new_bus)
        return -ENODEV;
    ret = of_mdiobus_register(new_bus, pdev->dev.of_node);
    if (ret)
        mdio_gpio_bus_deinit(&pdev->dev);
    return ret;
}
static int mdio_gpio_remove(struct platform_device *pdev)
{
    mdio_gpio_bus_destroy(&pdev->dev);
    return 0;
}
static const struct of_device_id mdio_gpio_of_match[] = {
    { .compatible = "virtual,mdio-gpio", },
    { /* sentinel */ }
};
MODULE_DEVICE_TABLE(of, mdio_gpio_of_match);
static struct platform_driver mdio_gpio_driver = {
    .probe = mdio_gpio_probe,
    .remove = mdio_gpio_remove,
    .driver     = {
        .name   = "mdio-gpio",
        .of_match_table = mdio_gpio_of_match,
    },
};
module_platform_driver(mdio_gpio_driver);
MODULE_ALIAS("platform:mdio-gpio");
MODULE_AUTHOR("Laurent Pinchart, Paulius Zaleckas");
MODULE_LICENSE("GPL v2");
MODULE_DESCRIPTION("Generic driver for MDIO bus emulation using GPIO");

DTS 配置

参考 Documentation/devicetree/bindings/net/mdio-gpio.txt 配置即可

MII 接口解析(一)MII 等类型接口简介
MII 接口解析(二)MII 数据类型和寄存器
MII 接口解析(三)GPIO 模拟 MDIO 接口使用代码

上一篇下一篇

猜你喜欢

热点阅读