一.硬件原理图
分析:
该交换芯片支持I2C、SPI、mdio通信,但是看ast1520的uboot代码采用的是mdio去通信phy芯片的,所以暂时也先采用mdio的方式,需要配置相应的引脚才可以配置成mdio通信模式,具体的配置硬件工程师解决。
背景:
RTL8306M芯片上可能没有提供MDC/MDIO接口,可以通过GPIO(General Purpose Input/Output)来模拟,GPIO可实现串行输入输出,且一般CPU上会提供很多GPIO接口供用户自定义使用。每组SMI需要两个GPIO口分别来模拟MDC和MDIO,首先需要保证这两个GPIO口不作其他用途,且相应的复用模式设置为GPIO模式。
二.SMI[MDC/MDIO]协议
SMI:串行管理接口(Serial Management Interface),通常直接被称为MDIO接口(Management Data Input/Output Interface)。MDIO最早在IEEE 802.3的第22卷定义,后来在第45卷又定义了增强版本的MDIO,其主要被应用于以太网的MAC和PHY层之间,用于MAC层器件通过读写寄存器来实现对PHY层器件的操作与管理。
MDIO接口包括两条线,MDIO和MDC,其中MDIO是双向数据线,而MDC是由STA驱动的时钟线。MDC时钟的最高速率一般为2.5MHz,MDC也可以是非固定频率,甚至可以是非周期的。MDIO接口只是会在MDC时钟的上升沿进行采样,而并不在意MDC时钟的频率(类似于I2C接口)。MDIO是一个PHY的管理接口,用来读/写PHY的寄存器,以控制PHY的行为或获取PHY的状态,MDC为MDIO提供时钟。
MAC读取PHY的寄存器:
MAC向PHY的寄存器写入数据:
三.代码实现
原厂提供了芯片RTL8306M的读写逻辑,需要自己实现gpio模拟MDC/MDIO和驱动入口。
Kconfig:
config RTL8309
tristate "RTL8309 driver"
default m
help
Enable this driver will support network switch control
Makefile:
# SPDX-License-Identifier: GPL-2.0-only
#
# Makefile for the Realtek network device drivers.
#
obj-$(CONFIG_RTL8309) += rtl8309.o
rtl8309-objs := rtl8309_main.o mdcmdio.o rtk_api.o rtl8309n_asicdrv.
DTS配置:
rtl_8309: rtl-8309{
status ="okay";
compatible = "rtl8309";
pinctrl-names = "default";
pinctrl-0 = <&rtl8309_mdio_pin
&rtl8309_mdc_pin>;
rtl8309-mdio-gpios = <&gpio3 RK_PC3 GPIO_ACTIVE_HIGH>;
rtl8309-mdc-gpios = <&gpio3 RK_PC2 GPIO_ACTIVE_HIGH>;
};
rtl-8309 {
rtl8309_mdio_pin:rtl8309-mdio-gpios{
rockchip,pins = <3 RK_PC3 RK_FUNC_GPIO &pcfg_pull_up>;
};
rtl8309_mdc_pin:rtl8309-mdc-gpios{
rockchip,pins = <3 RK_PC2 RK_FUNC_GPIO &pcfg_pull_up>;
};
};
实现一个杂散类设备RTL8306M:
#include <linux/miscdevice.h>
#include <linux/cdev.h>
#include <linux/device.h>
#include <linux/fs.h>
#include <linux/i2c-dev.h>
#include <linux/i2c.h>
#include <linux/init.h>
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/notifier.h>
#include <linux/slab.h>
#include <linux/uaccess.h>
#include <linux/compat.h>
#include <linux/printk.h>
#include <linux/kobject.h>
#include <linux/version.h>
#include <linux/kthread.h>
#include <linux/gpio.h>
#include <linux/of_gpio.h>
#include <linux/of.h>
#include <linux/of_device.h>
#include <linux/platform_device.h>
#include "rtk_api.h"
#include "rtl8309_main.h"
#include <linux/delay.h>
#define DRIVER_NAME "RTL8309"
#define RTL8309_DEBUG 1
#if RTL8309_DEBUG
#define DBG(format, args...) \
printk(KERN_DEBUG "%s: " format, DRIVER_NAME, ##args)
#define ERR(format, args...) \
printk(KERN_ERR "%s: " format, DRIVER_NAME, ##args)
#define WARNING(format, args...) \
printk(KERN_WARN "%s: " format, DRIVER_NAME, ##args)
#define INFO(format, args...) \
printk(KERN_INFO "%s: " format, DRIVER_NAME, ##args)
#else
#define DBG(format, args...)
#define ERR(format, args...)
#define WARNING(format, args...)
#define INFO(format, args...)
#endif
#define LINK_UP 1
#define LINK_DOWN 2
#define LINK_UNKNOW 3
int PRE_PORT_STATUS[5]={0,0,0,0,0};
struct rtl8309_dev {
struct device *dev;
struct device sys_dev;
struct gpio_desc *rtl8309_mdio;
struct gpio_desc *rtl8309_mdc;
struct delayed_work check_status_work;
struct mutex status_lock;
u32 link_status;
};
struct rtl8309_dev *g_rtl8309;
struct rtl8309_dev *rtl8309;
void setGpioDirection(int gpio, uint32_t dir)
{
if(gpio == IST_GPIO_RTL8309_MDIO){
if(dir){
gpiod_direction_output(g_rtl8309->rtl8309_mdio,1);
}else{
gpiod_direction_input(g_rtl8309->rtl8309_mdio);
}
}else if(gpio == IST_GPIO_RTL8309_MDC){
if(dir){
gpiod_direction_output(g_rtl8309->rtl8309_mdc,1);
}else{
gpiod_direction_input(g_rtl8309->rtl8309_mdc);
}
}
}
void setGpioOutput(int gpio, uint32_t out )
{
if(gpio == IST_GPIO_RTL8309_MDIO){
gpiod_set_value(g_rtl8309->rtl8309_mdio, out);
}else if(gpio == IST_GPIO_RTL8309_MDC){
gpiod_set_value(g_rtl8309->rtl8309_mdc, out);
}
}
void getGpioInput(int gpio,uint32_t *in)
{
if(gpio == IST_GPIO_RTL8309_MDIO){
*in = gpiod_get_value(g_rtl8309->rtl8309_mdio);
}else if(gpio == IST_GPIO_RTL8309_MDC){
*in = gpiod_get_value(g_rtl8309->rtl8309_mdc);
}
}
static long rtl8309_ioctl(struct file *file, uint32_t cmd, unsigned long arg)
{
return 0;
}
static ssize_t rtl8309_write(struct file *file, const char __user *buf,
size_t size, loff_t *ppos)
{
return 1;
}
static ssize_t rtl8309_read(struct file *file, char __user *buf, size_t size,
loff_t *ppos)
{
return 1;
}
static void check_link_status(struct work_struct *work)
{
struct delayed_work *dwork = to_delayed_work(work);
struct rtl8309_dev *rtl8309 =
container_of(dwork, struct rtl8309_dev, check_status_work);
int PORT_ENABLE[5]={0,1,0,1,0};
int PORT_STATUS[5]={0,0,0,0,0};
int status = 0;
int i = 0;
rtk_port_linkStatus_t pLinkStatus = PORT_LINKDOWN;
rtk_port_speed_t pSpeed = PORT_SPEED_10M;
rtk_port_duplex_t pDuplex = PORT_HALF_DUPLEX;
mutex_lock(&rtl8309->status_lock);
for(i=0;i<5;i++){
if(PORT_ENABLE[i] == 1){
if(rtk_port_phyStatus_get((rtk_port_t)i, &pLinkStatus, &pSpeed, &pDuplex) !=0 ){
printk("rtk_port_phyStatus_get error:%d",i);
break;
}
printk("GetEthStatus, port= %d, pLinkStatus= %d, pSpeed= %d, pDuplex= %d \n",i, pLinkStatus, pSpeed, pDuplex);
if(pLinkStatus == PORT_LINKUP){
PORT_STATUS[i] = 1;
printk("port_status\n");
if(PRE_PORT_STATUS[i] != PORT_STATUS[i]){
printk("port[%d] status changed, set led mode\n",i);
if(pSpeed == PORT_SPEED_100M){
printk("port_speed\n");
}else{
printk("port_speed error\n");
}
}
}else{
PORT_STATUS[i] = 0;
printk("port_status\n");
}
PRE_PORT_STATUS[i] = PORT_STATUS[i];
msleep(10);
}
}
mutex_unlock(&rtl8309->status_lock);
for(i=0;i<5;i++){
status = status | PORT_STATUS[i];
}
if(status == 0)
rtl8309->link_status = LINK_DOWN;
else if(status == 1)
rtl8309->link_status = LINK_UP;
else
rtl8309->link_status = LINK_UNKNOW;
schedule_delayed_work(&rtl8309->check_status_work, msecs_to_jiffies(5000));
}
static ssize_t rtl8309_status_read(struct device *dev,
struct device_attribute *attr, char *buf)
{
struct rtl8309_dev *rtl8309 = g_rtl8309;
printk("rtl8309_status_read rtl8309_status_read:%d\n",rtl8309->link_status);
return sprintf(buf, "%d\n", rtl8309->link_status);
}
static ssize_t rtl8309_status_write(struct device *dev,
struct device_attribute *attr,
const char *buf, size_t count)
{
printk("rtl8309_status_write rtl8309_status_write");
return count;
}
static DEVICE_ATTR(linkstatus, 0644,
rtl8309_status_read, rtl8309_status_write);
static const struct file_operations rtl8309_fops = {
.owner = THIS_MODULE,
.read = rtl8309_read,
.write = rtl8309_write,
.unlocked_ioctl = rtl8309_ioctl,
};
struct miscdevice rtl8309_miscdev = {
.minor = MISC_DYNAMIC_MINOR,
.name = "rtl8309_dev",
.fops = &rtl8309_fops,
};
static int rtl8309_probe(struct platform_device *pdev)
{
printk("rtl8309_probe");
struct rtl8309_dev *rtl8309;
int ret = 0;
rtl8309 = devm_kzalloc(&pdev->dev, sizeof(*rtl8309), GFP_KERNEL);
if (!rtl8309)
return -ENOMEM;
rtl8309->dev = &pdev->dev;
rtl8309->rtl8309_mdio = devm_gpiod_get_optional(rtl8309->dev,
"rtl8309-mdio", GPIOD_OUT_HIGH);
if (IS_ERR(rtl8309->rtl8309_mdio)) {
printk("Could not get rtl8367-mdio");
rtl8309->rtl8309_mdio = NULL;
}
rtl8309->rtl8309_mdc = devm_gpiod_get_optional(rtl8309->dev,
"rtl8309-mdc", GPIOD_OUT_HIGH);
if (IS_ERR(rtl8309->rtl8309_mdc)) {
printk("Could not get rtl8367-mdc ");
rtl8309->rtl8309_mdc = NULL;
}
g_rtl8309 = rtl8309;
ret = misc_register(&rtl8309_miscdev);
if (ret) {
ERR("rtl8309_miscdev ERROR: could not register rtl8309_miscdev device\n");
return ret;
}
ret = device_create_file(rtl8309_miscdev.this_device,
&dev_attr_linkstatus);
if (ret) {
printk("failed to create attr linkstatus");
return ret;
}
mutex_init(&rtl8309->status_lock);
INIT_DELAYED_WORK(&rtl8309->check_status_work, check_link_status);
schedule_delayed_work(&rtl8309->check_status_work, msecs_to_jiffies(5000));
return 0;
}
static int rtl8309_remove(struct platform_device *client)
{
return 0;
}
static const struct of_device_id rtl8309_of_match[] = {
{ .compatible = "rtl8309" },
{}
};
MODULE_DEVICE_TABLE(of, rtl8309_of_match);
static struct platform_driver rtl8309_driver = {
.probe = rtl8309_probe,
.remove = rtl8309_remove,
.driver = {
.owner = THIS_MODULE,
.name = DRIVER_NAME,
.of_match_table = of_match_ptr(rtl8309_of_match),
},
};
static int __init rtl8309_init(void)
{
int ret = platform_driver_register(&rtl8309_driver);
if (ret != 0) {
pr_err("Failed to register example driver: %d\n", ret);
return ret;
}
pr_info("Example driver initialized\n");
return 0;
}
static void __exit rtl8309_exit(void)
{
platform_driver_unregister(&rtl8309_driver);
pr_info("Example driver exited\n");
}
module_init(rtl8309_init);
module_exit(rtl8309_exit);
MODULE_DESCRIPTION("rtl8309 GPIO Switch");
MODULE_AUTHOR("Zewei Ye <yezw@ist.com.hk>");
MODULE_LICENSE("GPL v2");
函数说明:rtk_port_phyStatus_get为原厂提供的读取RTL8306M芯片状态的接口,根据返回的状态判断是否有百兆网。