blob: fa22fc3b2768b83d42d4846ced25f4dc72f439d2 [file] [log] [blame]
/*
* drivers/amlogic/ethernet/phy/phy_debug.c
*
* Copyright (C) 2017 Amlogic, Inc. All rights reserved.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
* more details.
*
*/
#include <linux/clk.h>
#include <linux/kernel.h>
#include <linux/interrupt.h>
#include <linux/ip.h>
#include <linux/tcp.h>
#include <linux/skbuff.h>
#include <linux/ethtool.h>
#include <linux/if_ether.h>
#include <linux/crc32.h>
#include <linux/mii.h>
#include <linux/if.h>
#include <linux/if_vlan.h>
#include <linux/dma-mapping.h>
#include <linux/slab.h>
#include <linux/prefetch.h>
#include <linux/pinctrl/consumer.h>
#include <linux/net_tstamp.h>
#include <linux/reset.h>
#include <linux/of_mdio.h>
#ifdef CONFIG_DWMAC_MESON
#include <linux/amlogic/cpu_version.h>
#include <linux/amlogic/iomap.h>
#include "phy_debug.h"
void __iomem *PREG_ETH_REG0;
void __iomem *PREG_ETH_REG1;
static int g_phyreg;
static void __iomem *c_ioaddr;
static ssize_t show_phy_reg(
struct device *dev,
struct device_attribute *attr, char *buf)
{
return snprintf(buf, PAGE_SIZE, "current phy reg = 0x%x\n", g_phyreg);
}
static ssize_t set_phy_reg(
struct device *dev,
struct device_attribute *attr,
const char *buf, size_t count)
{
int ovl;
int r = kstrtoint(buf, 0, &ovl);
if (r) {
pr_err("kstrtoint failed\n");
return -1;
}
g_phyreg = ovl;
pr_info("---ovl=0x%x\n", ovl);
return count;
}
static ssize_t show_phy_reg_value(
struct device *dev,
struct device_attribute *attr, char *buf)
{
struct phy_device *phy_dev = dev_get_drvdata(dev);
int ret, val, i;
for (i = 0; i < 32; i++)
pr_info("%d: 0x%x\n", i, phy_read(phy_dev, i));
val = phy_read(phy_dev, g_phyreg);
ret = snprintf(buf, PAGE_SIZE, "phy reg 0x%x = 0x%x\n", g_phyreg, val);
return ret;
}
static ssize_t set_phy_reg_value(
struct device *dev,
struct device_attribute *attr,
const char *buf, size_t count)
{
int ovl;
int ret;
struct phy_device *phy_dev = dev_get_drvdata(dev);
ret = kstrtoint(buf, 0, &ovl);
pr_info("---reg 0x%x: ovl=0x%x\n", g_phyreg, ovl);
phy_write(phy_dev, g_phyreg, ovl);
return count;
}
static struct device_attribute phy_reg_attrs[] = {
__ATTR(phy_reg, 0644, show_phy_reg, set_phy_reg),
__ATTR(phy_reg_value, 0644, show_phy_reg_value, set_phy_reg_value)
};
static struct phy_device *c_phy_dev;
void am_net_dump_phyreg(void)
{
int reg = 0;
int val = 0;
if (!c_phy_dev)
return;
pr_info("========== ETH PHY new regs ==========\n");
for (reg = 0; reg < 32; reg++) {
val = phy_read(c_phy_dev, reg);
pr_info("[reg_%d] 0x%x\n", reg, val);
}
}
static int am_net_read_phyreg(int argc, char **argv)
{
int reg = 0;
int val = 0;
int r = 0;
if (!c_phy_dev)
return -1;
if (argc < 2 || !argv || !argv[0] || !argv[1]) {
pr_err("Invalid syntax\n");
return -1;
}
r = kstrtoint(argv[1], 0, &reg);
if (r) {
pr_err("kstrtoint failed\n");
return -1;
}
if (reg >= 0 && reg <= 31) {
val = phy_read(c_phy_dev, reg);
pr_info("read phy [reg_%d] 0x%x\n", reg, val);
} else if (reg == 32) {
pr_info("phy features:0x%x\n", c_phy_dev->drv->features);
} else {
pr_info("Invalid parameter\n");
}
return 0;
}
static int am_net_write_phyreg(int argc, char **argv)
{
int reg = 0;
int val = 0;
int r = 0;
if (!c_phy_dev)
return -1;
if (argc < 3 || !argv || !argv[0] || !argv[1] || !argv[2]) {
pr_err("Invalid syntax\n");
return -1;
}
r = kstrtoint(argv[1], 0, &reg);
if (r) {
pr_err("kstrtoint failed\n");
return -1;
}
r = kstrtoint(argv[2], 0, &val);
if (r) {
pr_err("kstrtoint failed\n");
return -1;
}
if (reg >= 0 && reg <= 31) {
phy_write(c_phy_dev, reg, val);
pr_info(
"write phy [reg_%d] 0x%x, 0x%x\n",
reg, val, phy_read(c_phy_dev, reg));
} else if (reg == 32) {
if (val > 255) {
pr_info("Invalid parameter\n");
return 0;
}
c_phy_dev->drv->features &= 0xff;
c_phy_dev->drv->features |= val << 8;
} else if (reg == 33) {
if (val > 255) {
pr_info("Invalid parameter\n");
return 0;
}
c_phy_dev->drv->features &= 0xff00;
c_phy_dev->drv->features |= val;
} else {
pr_info("Invalid parameter\n");
}
return 0;
}
void am_net_dump_phy_wol_reg(void)
{
int reg;
int val;
int val15;
int val16;
if (!c_phy_dev)
return;
pr_info("========== ETH PHY wol regs ==========\n");
for (reg = 0; reg < 13; reg++) {
/*Bit 15: READ*/
/*Bit 14: Write*/
/*Bit 12:11: BANK_SEL (0: DSP, 1: WOL, 3: BIST)*/
/*Bit 10: Test Mode*/
/*Bit 9:5: Read Address*/
/*Bit 4:0: Write Address*/
val = (1 << 15) | (1 << 11) | (1 << 10) | (reg << 5);
phy_write(c_phy_dev, 0x14, val);
val15 = phy_read(c_phy_dev, 0x15);
val16 = phy_read(c_phy_dev, 0x16);
val = val16 * 65536 + val15;
pr_info("wol [reg_%d] 0x%x\n", reg, val);
}
}
void am_net_dump_phy_bist_reg(void)
{
int reg;
int val;
int val15;
int val16;
if (!c_phy_dev)
return;
pr_info("========== ETH PHY bist regs ==========\n");
for (reg = 0; reg < 32; reg++) {
/*Bit 15: READ*/
/*Bit 14: Write*/
/*Bit 12:11: BANK_SEL (0: DSP, 1: WOL, 3: BIST)*/
/*Bit 10: Test Mode*/
/*Bit 9:5: Read Address*/
/*Bit 4:0: Write Address*/
val = ((1 << 15) | (1 << 12) | (1 << 11) |
(1 << 10) | (reg << 5));
phy_write(c_phy_dev, 0x14, val);
val15 = phy_read(c_phy_dev, 0x15);
val16 = phy_read(c_phy_dev, 0x16);
val = val16 * 65536 + val15;
pr_info("bist [reg_%d] 0x%x\n", reg, val);
}
}
static int read_tst_cntl_reg(int argc, char **argv)
{
int rd_data_hi;
int rd_addr;
int rd_data = 0;
if (argc < 2 || (!argv) || (!argv[0]) || (!argv[1])) {
pr_info("Invalid syntax\n");
return -1;
}
if (kstrtoint(argv[1], 16, &rd_addr) < 0)
return -1;
if (rd_addr >= 0 && rd_addr <= 31) {
phy_write(
c_phy_dev, 20,
((1 << 15) | (1 << 10) | ((rd_addr & 0x1f) << 5)));
rd_data = phy_read(c_phy_dev, 21);
rd_data_hi = phy_read(c_phy_dev, 22);
rd_data = ((rd_data_hi & 0xffff) << 16) | rd_data;
pr_info("read tstcntl phy [reg_%d] 0x%x\n", rd_addr, rd_data);
} else {
pr_info("Invalid parameter\n");
}
return rd_data;
}
static int return_write_val(int rd_addr)
{
int rd_data;
int rd_data_hi;
phy_write(
c_phy_dev, 20,
((1 << 15) | (1 << 10) | ((rd_addr & 0x1f) << 5)));
rd_data = phy_read(c_phy_dev, 21);
rd_data_hi = phy_read(c_phy_dev, 22);
rd_data = ((rd_data_hi & 0xffff) << 16) | rd_data;
return rd_data;
}
static int write_tst_cntl_reg(int argc, char **argv)
{
int wr_addr, wr_data;
if (argc < 3 || (!argv) || (!argv[0]) || (!argv[1]) || (!argv[2])) {
pr_info("Invalid syntax\n");
return -1;
}
if (kstrtoint(argv[1], 16, &wr_addr) < 0)
return -1;
if (kstrtoint(argv[2], 16, &wr_data) < 0)
return -1;
if (wr_addr >= 0 && wr_addr <= 31) {
phy_write(c_phy_dev, 23, (wr_data & 0xffff));
phy_write(
c_phy_dev, 20,
((1 << 14) | (1 << 10) | ((wr_addr << 0) & 0x1f)));
pr_info("write phy tstcntl [reg_%d] 0x%x, 0x%x\n",
wr_addr, wr_data, return_write_val(wr_addr));
} else {
pr_info("Invalid parameter\n");
}
return 0;
}
static void tstcntl_dump_phyreg(void)
{
int rd_addr;
int rd_data;
int rd_data_hi;
pr_info("========== ETH TST PHY regs ==========\n");
for (rd_addr = 0; rd_addr < 32; rd_addr++) {
phy_write(
c_phy_dev, 20,
((1 << 15) | (1 << 10) | ((rd_addr & 0x1f) << 5)));
rd_data = phy_read(c_phy_dev, 21);
rd_data_hi = phy_read(c_phy_dev, 22);
rd_data = ((rd_data_hi & 0xffff) << 16) | rd_data;
pr_info("tstcntl phy [reg_%d] 0x%x\n", rd_addr, rd_data);
}
}
void am_net_dump_phy_extended_reg(void)
{
int reg;
int val;
int val15;
int val16;
if (!c_phy_dev)
return;
pr_info("========== ETH PHY extended regs ==========\n");
for (reg = 0; reg < 32; reg++) {
/*Bit 15: READ*/
/*Bit 14: Write*/
/*Bit 12:11: BANK_SEL (0: DSP, 1: WOL, 3: BIST)*/
/*Bit 10: Test Mode*/
/*Bit 9:5: Read Address*/
/*Bit 4:0: Write Address*/
val = ((1 << 15) | (1 << 10) | (reg << 5));
phy_write(c_phy_dev, 0x14, val);
val15 = phy_read(c_phy_dev, 0x15);
val16 = phy_read(c_phy_dev, 0x16);
val = (val16 << 16) + val15;
pr_info("extended [reg_%d] 0x%x\n", reg, val);
}
}
static void am_net_dump_macreg(void)
{
int reg;
int val;
pr_info("========== ETH_MAC regs ==========\n");
for (reg = ETH_MAC_0_Configuration;
reg <= ETH_MMC_rxicmp_err_octets; reg += 0x4) {
val = readl(c_ioaddr + reg);
pr_info("[0x%04x] 0x%x\n", reg, val);
}
pr_info("========== ETH_DMA regs ==========\n");
for (reg = ETH_DMA_0_Bus_Mode;
reg <= ETH_DMA_21_Curr_Host_Re_Buffer_Addr; reg += 0x4) {
val = readl(c_ioaddr + reg);
pr_info("[0x%04x] 0x%x\n", reg, val);
}
}
static int am_net_read_macreg(int argc, char **argv)
{
int reg = 0;
int val = 0;
int r = 0;
if (argc < 2 || (!argv) || (!argv[0]) || (!argv[1])) {
pr_err("Invalid syntax\n");
return -1;
}
r = kstrtoint(argv[1], 0, &reg);
if (r) {
pr_err("kstrtoint failed\n");
return -1;
}
if (reg >= 0 && reg <= ETH_DMA_21_Curr_Host_Re_Buffer_Addr) {
val = readl(c_ioaddr + reg);
pr_info("read mac [0x4%x] 0x%x\n", reg, val);
} else {
pr_info("Invalid parameter\n");
}
return 0;
}
static int am_net_write_macreg(int argc, char **argv)
{
int reg;
int val;
int r;
if ((argc < 3) || (!argv) || (!argv[0]) || (!argv[1]) || (!argv[2])) {
pr_err("Invalid syntax\n");
return -1;
}
r = kstrtoint(argv[1], 0, &reg);
if (r) {
pr_err("kstrtoint failed\n");
return -1;
}
r = kstrtoint(argv[2], 0, &val);
if (r) {
pr_err("kstrtoint failed\n");
return -1;
}
if (reg >= 0 && reg <= ETH_DMA_21_Curr_Host_Re_Buffer_Addr) {
writel(val, (c_ioaddr + reg));
pr_info("write mac [0x%x] 0x%x, 0x%x\n",
reg, val, readl(c_ioaddr + reg));
} else {
pr_info("Invalid parameter\n");
}
return 0;
}
static const char *g_phyreg_help = {
"Usage:\n"
" echo d > phyreg; //dump ethernet phy reg\n"
" echo r reg > phyreg; //read ethernet phy reg\n"
" echo w reg val > phyreg; //write ethernet phy reg\n"
};
static ssize_t eth_phyreg_help(
struct class *class, struct class_attribute *attr, char *buf)
{
return sprintf(buf, "%s\n", g_phyreg_help);
}
static unsigned char adc_data[32 * 32];
static unsigned char adc_freq[64];
static inline void am_init_tst_mode(void)
{
phy_write(c_phy_dev, 20, 0x0400);
phy_write(c_phy_dev, 20, 0x0000);
phy_write(c_phy_dev, 20, 0x0400);
}
static inline void am_close_tst_mode(void)
{
phy_write(c_phy_dev, 20, 0x0000);
}
static void am_net_adc_show(void)
{
int rd_data = 0;
int rd_data_hi = 0;
int i, j, v;
char buf[128];
pr_info("%s, %d enter\n", __func__, __LINE__);
am_init_tst_mode();
for (i = 0; i < (32 * 32); i++) {
v = (1 << 15) | (1 << 10) | ((16 & 0x1f) << 5);
phy_write(c_phy_dev, 20, v);
rd_data = phy_read(c_phy_dev, 21);
rd_data_hi = phy_read(c_phy_dev, 22);
adc_data[i] = rd_data & 0x3f;
}
am_close_tst_mode();
for (i = 0; i < 32; i++) {
for (j = 0; j < 32; j++)
pr_info("%02x ", adc_data[i * 32 + j]);
pr_info("\n");
}
for (i = 0; i < 64; i++)
adc_freq[i] = 0;
for (i = 0; i < 32; i++) {
for (j = 0; j < 32; j++) {
if (adc_data[i * 32 + j] > 31)
adc_freq[adc_data[i * 32 + j] - 32]++;
else
adc_freq[32 + adc_data[i * 32 + j]]++;
pr_info("%02x ", adc_data[i * 32 + j]);
}
pr_info("\n");
}
for (i = 0; i < 64; i++) {
pr_info("%d(%04d):\t", i - 32, adc_freq[i]);
if (adc_freq[i] > 128)
adc_freq[i] = 128;
for (j = 0; j < adc_freq[i]; j++)
buf[j] = '#';
buf[j] = '\0';
pr_info("%s\n", buf);
}
}
static void am_net_eye_pattern_on(void)
{
unsigned int value;
c_phy_dev->autoneg = AUTONEG_DISABLE;
/*stop check wol when doing eye pattern, otherwise it will reset phy*/
enable_wol_check = 0;
phy_write(c_phy_dev, 0, 0x2100);
value = phy_read(c_phy_dev, 17);
pr_info("0x11 %x\n", value);
phy_write(c_phy_dev, 17, (value | 0x0004));
}
static void am_net_eye_pattern_off(void)
{
unsigned int value;
c_phy_dev->autoneg = AUTONEG_ENABLE;
enable_wol_check = 1;
phy_write(c_phy_dev, 0, 0x1000);
value = phy_read(c_phy_dev, 17);
pr_info("0x11 %x\n", value);
phy_write(c_phy_dev, 17, (value & ~0x0004));
}
static void am_net_debug_mode(void)
{ /*disable autoneg*/
c_phy_dev->autoneg = AUTONEG_DISABLE;
/*disable wol check*/
enable_wol_check = 0;
}
static ssize_t eth_phyreg_func(
struct class *class, struct class_attribute *attr,
const char *buf, size_t count)
{
int argc;
char *buff, *p, *para;
char *argv[4];
char cmd;
buff = kstrdup(buf, GFP_KERNEL);
p = buff;
for (argc = 0; argc < 4; argc++) {
para = strsep(&p, " ");
if (!para)
break;
argv[argc] = para;
}
if (argc < 1 || argc > 4)
goto end;
cmd = argv[0][0];
switch (cmd) {
case 'r':
case 'R':
am_net_read_phyreg(argc, argv);
break;
case 'w':
case 'W':
am_net_write_phyreg(argc, argv);
break;
case 'd':
case 'D':
am_net_dump_phyreg();
break;
#ifdef CONFIG_MESON_ETH_WOL /* FIXED ME */
case 'p':
wol_test(c_phy_dev);
break;
#endif
case 'e':
am_net_dump_phy_extended_reg();
am_net_dump_phy_wol_reg();
am_net_dump_phy_bist_reg();
break;
case 'c':
case 'C':
am_net_adc_show();
break;
case 't':
case 'T':
am_init_tst_mode();
if (argv[0][1] == 'w' || argv[0][1] == 'W')
write_tst_cntl_reg(argc, argv);
if (argv[0][1] == 'r' || argv[0][1] == 'R')
read_tst_cntl_reg(argc, argv);
if (argv[0][1] == 'd' || argv[0][1] == 'D')
tstcntl_dump_phyreg();
am_close_tst_mode();
break;
case 'o':
case 'O':
if (argv[0][1] == 'n' || argv[0][1] == 'N') {
am_net_eye_pattern_on();
break;
}
if (argv[0][1] == 'f' || argv[0][1] == 'F') {
am_net_eye_pattern_off();
break;
}
if (argv[0][1] == 'd' || argv[0][1] == 'D') {
am_net_debug_mode();
break;
}
break;
default:
goto end;
}
return count;
end:
kfree(buff);
return 0;
}
static const char *g_macreg_help = {
"Usage:\n"
" echo d > macreg; //dump ethernet mac reg\n"
" echo r reg > macreg; //read ethernet mac reg\n"
" echo w reg val > macreg; //read ethernet mac reg\n"
};
static ssize_t eth_macreg_help(
struct class *class,
struct class_attribute *attr, char *buf)
{
return sprintf(buf, "%s\n", g_macreg_help);
}
static ssize_t eth_macreg_func(
struct class *class, struct class_attribute *attr,
const char *buf, size_t count)
{
int argc;
char *buff, *p, *para;
char *argv[4];
char cmd;
buff = kstrdup(buf, GFP_KERNEL);
p = buff;
for (argc = 0; argc < 4; argc++) {
para = strsep(&p, " ");
if (!para)
break;
argv[argc] = para;
}
if (argc < 1 || argc > 4)
goto end;
cmd = argv[0][0];
switch (cmd) {
case 'r':
case 'R':
am_net_read_macreg(argc, argv);
break;
case 'w':
case 'W':
am_net_write_macreg(argc, argv);
break;
case 'd':
case 'D':
am_net_dump_macreg();
break;
default:
goto end;
}
return count;
end:
kfree(buff);
return 0;
}
static ssize_t eth_linkspeed_show(
struct class *class, struct class_attribute *attr, char *buf)
{
int ret;
char buff[100];
if (c_phy_dev) {
phy_print_status(c_phy_dev);
genphy_update_link(c_phy_dev);
if (c_phy_dev->link)
strcpy(buff, "link status: link\n");
else
strcpy(buff, "link status: unlink\n");
} else {
strcpy(buff, "link status: unlink\n");
}
ret = sprintf(buf, "%s\n", buff);
return ret;
}
int auto_cali(void)
{
unsigned int value;
int I1, I2, I3, I4, I5;
int count = 99;
char problem[20] = {0};
char path[20] = {0};
int cali_rise = 0;
int cali_sel = 0;
pr_info("auto test cali\n");
for (cali_sel = 0; cali_sel < 4; cali_sel++) {
readl(PREG_ETH_REG1);
strcpy(problem, "no clock delay");
value = readl(PREG_ETH_REG0) & (~(0x1f << 25));
writel(value, PREG_ETH_REG0);
I1 = 0;
I2 = 0;
I3 = 0;
I4 = 0;
I5 = 0;
for (cali_rise = 0; cali_rise <= 1; cali_rise++) {
count = 99;
value = (readl(PREG_ETH_REG0) | (1 << 25) |
(cali_rise << 26) | (cali_sel << 27));
writel(value, PREG_ETH_REG0);
while (count >= 0) {
value = readl(PREG_ETH_REG1);
if ((value >> 15) & 0x1) {
count--;
switch (value & 0x1f) {
case 0x0:
I1++;
break;
case 0x1:
I2++;
break;
case 0x2:
I3++;
break;
case 0x3:
I4++;
break;
case 0x4:
I5++;
break;
}
}
}
pr_info(" I1 = %d; I2 = %d; I3 = %d; I4 = %d; I5 = %d;\n",
I1, I2, I3, I4, I5);
if (
(I1 > 0) && (I2 > 0) && (I3 > 0) &&
(I4 > 0) && (I5 > 0))
strcpy(problem, "clock delay");
pr_info(" RXDATA Line %d have %s problem\n",
cali_sel, problem);
strcpy(path, (I2 + I1 + I3) > (I5 + I4 + I3) ?
"positive" : "opposite");
if (!strncmp(problem, "clock delay", 11))
pr_info("Need debug to delay %s direction\n", path);
}
}
return 0;
}
static int am_net_cali(int argc, char **argv, int gate)
{
unsigned int value;
int cali_rise = 0;
int cali_sel = 0;
int cali_start = 0;
int cali_time = 0;
int ii = 0;
int r = 0;
cali_start = gate;
if (gate == 3) {
auto_cali();
return 0;
}
if (
(argc < 4) || (!argv) || (!argv[0]) ||
(!argv[1]) || (!argv[2]) || (!argv[3])) {
pr_err("Invalid syntax\n");
return -1;
}
r = kstrtoint(argv[1], 0, &cali_rise);
r = kstrtoint(argv[2], 0, &cali_sel);
r = kstrtoint(argv[3], 0, &cali_time);
readl(PREG_ETH_REG1);
value = readl(PREG_ETH_REG0) & (~(0x1f << 25));
writel(value, PREG_ETH_REG0);
value = readl(PREG_ETH_REG0) | (cali_start << 25) |
(cali_rise << 26) | (cali_sel << 27);
writel(value, PREG_ETH_REG0);
pr_info("rise :%d sel: %d time: %d start:%d cbus2050 = %x\n",
cali_rise, cali_sel, cali_time, cali_start,
readl(PREG_ETH_REG0));
for (ii = 0; ii < cali_time; ii++) {
mdelay(100);
value = readl(PREG_ETH_REG1);
if ((value >> 15) & 0x1) {
pr_info
("value = %x,len = %d,idx = %d,sel=%d,rise = %d\n",
value, (value >> 5) & 0x1f, (value & 0x1f),
(value >> 11) & 0x7, (value >> 14) & 0x1);
ii++;
}
}
return 0;
}
static ssize_t eth_cali_store(
struct class *class, struct class_attribute *attr,
const char *buf, size_t count)
{
int argc;
char *buff, *p, *para;
char *argv[5];
char cmd;
buff = kstrdup(buf, GFP_KERNEL);
p = buff;
if (get_cpu_type() < MESON_CPU_MAJOR_ID_M8B) {
pr_err("Sorry ,this cpu is not support cali!\n");
goto end;
}
for (argc = 0; argc < 5; argc++) {
para = strsep(&p, " ");
if (!para)
break;
argv[argc] = para;
}
if (argc < 1 || argc > 4)
goto end;
cmd = argv[0][0];
switch (cmd) {
case 'e':
case 'E':
am_net_cali(argc, argv, 1);
break;
case 'd':
case 'D':
am_net_cali(argc, argv, 0);
break;
case 'a':
case 'A':
am_net_cali(argc, argv, 3);
break;
default:
goto end;
}
return count;
end:
kfree(buff);
return 0;
}
#define DRIVER_NAME "ethernet"
static struct class *phy_sys_class;
static CLASS_ATTR(phyreg, 0644, eth_phyreg_help, eth_phyreg_func);
static CLASS_ATTR(macreg, 0644, eth_macreg_help, eth_macreg_func);
static CLASS_ATTR(linkspeed, 0444, eth_linkspeed_show, NULL);
static CLASS_ATTR(cali, 0644, NULL, eth_cali_store);
int gmac_create_sysfs(struct phy_device *phydev, void __iomem *ioaddr)
{
int r;
int t;
int ret;
c_phy_dev = phydev;
c_ioaddr = ioaddr;
dev_set_drvdata(&phydev->mdio.dev, phydev);
for (t = 0; t < ARRAY_SIZE(phy_reg_attrs); t++) {
r = device_create_file(&phydev->mdio.dev, &phy_reg_attrs[t]);
if (r) {
dev_err(&phydev->mdio.dev, "failed to create sysfs file\n");
return r;
}
}
phy_sys_class = class_create(THIS_MODULE, DRIVER_NAME);
ret = class_create_file(phy_sys_class, &class_attr_phyreg);
ret = class_create_file(phy_sys_class, &class_attr_macreg);
ret = class_create_file(phy_sys_class, &class_attr_linkspeed);
ret = class_create_file(phy_sys_class, &class_attr_cali);
return 0;
}
int gmac_remove_sysfs(struct phy_device *phydev)
{
int t;
for (t = 0; t < ARRAY_SIZE(phy_reg_attrs); t++)
device_remove_file(&phydev->mdio.dev, &phy_reg_attrs[t]);
class_destroy(phy_sys_class);
c_phy_dev = NULL;
return 0;
}
#endif