at803x.c 5.5 KB
Newer Older
M
Matus Ujhelyi 已提交
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29
/*
 * drivers/net/phy/at803x.c
 *
 * Driver for Atheros 803x PHY
 *
 * Author: Matus Ujhelyi <ujhelyi.m@gmail.com>
 *
 * 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.
 */

#include <linux/phy.h>
#include <linux/module.h>
#include <linux/string.h>
#include <linux/netdevice.h>
#include <linux/etherdevice.h>

#define AT803X_INTR_ENABLE			0x12
#define AT803X_INTR_STATUS			0x13
#define AT803X_WOL_ENABLE			0x01
#define AT803X_DEVICE_ADDR			0x03
#define AT803X_LOC_MAC_ADDR_0_15_OFFSET		0x804C
#define AT803X_LOC_MAC_ADDR_16_31_OFFSET	0x804B
#define AT803X_LOC_MAC_ADDR_32_47_OFFSET	0x804A
#define AT803X_MMD_ACCESS_CONTROL		0x0D
#define AT803X_MMD_ACCESS_CONTROL_DATA		0x0E
#define AT803X_FUNC_DATA			0x4003
30 31 32 33
#define AT803X_DEBUG_ADDR			0x1D
#define AT803X_DEBUG_DATA			0x1E
#define AT803X_DEBUG_SYSTEM_MODE_CTRL		0x05
#define AT803X_DEBUG_RGMII_TX_CLK_DLY		BIT(8)
M
Matus Ujhelyi 已提交
34 35 36 37 38

MODULE_DESCRIPTION("Atheros 803x PHY driver");
MODULE_AUTHOR("Matus Ujhelyi");
MODULE_LICENSE("GPL");

39 40
static int at803x_set_wol(struct phy_device *phydev,
			  struct ethtool_wolinfo *wol)
M
Matus Ujhelyi 已提交
41 42 43
{
	struct net_device *ndev = phydev->attached_dev;
	const u8 *mac;
44 45
	int ret;
	u32 value;
M
Matus Ujhelyi 已提交
46 47 48 49 50 51 52
	unsigned int i, offsets[] = {
		AT803X_LOC_MAC_ADDR_32_47_OFFSET,
		AT803X_LOC_MAC_ADDR_16_31_OFFSET,
		AT803X_LOC_MAC_ADDR_0_15_OFFSET,
	};

	if (!ndev)
53
		return -ENODEV;
M
Matus Ujhelyi 已提交
54

55 56
	if (wol->wolopts & WAKE_MAGIC) {
		mac = (const u8 *) ndev->dev_addr;
M
Matus Ujhelyi 已提交
57

58 59
		if (!is_valid_ether_addr(mac))
			return -EFAULT;
M
Matus Ujhelyi 已提交
60

61 62
		for (i = 0; i < 3; i++) {
			phy_write(phydev, AT803X_MMD_ACCESS_CONTROL,
M
Matus Ujhelyi 已提交
63
				  AT803X_DEVICE_ADDR);
64
			phy_write(phydev, AT803X_MMD_ACCESS_CONTROL_DATA,
M
Matus Ujhelyi 已提交
65
				  offsets[i]);
66
			phy_write(phydev, AT803X_MMD_ACCESS_CONTROL,
M
Matus Ujhelyi 已提交
67
				  AT803X_FUNC_DATA);
68
			phy_write(phydev, AT803X_MMD_ACCESS_CONTROL_DATA,
M
Matus Ujhelyi 已提交
69
				  mac[(i * 2) + 1] | (mac[(i * 2)] << 8));
70 71 72 73 74 75 76 77 78 79 80 81 82 83 84
		}

		value = phy_read(phydev, AT803X_INTR_ENABLE);
		value |= AT803X_WOL_ENABLE;
		ret = phy_write(phydev, AT803X_INTR_ENABLE, value);
		if (ret)
			return ret;
		value = phy_read(phydev, AT803X_INTR_STATUS);
	} else {
		value = phy_read(phydev, AT803X_INTR_ENABLE);
		value &= (~AT803X_WOL_ENABLE);
		ret = phy_write(phydev, AT803X_INTR_ENABLE, value);
		if (ret)
			return ret;
		value = phy_read(phydev, AT803X_INTR_STATUS);
M
Matus Ujhelyi 已提交
85
	}
86 87 88 89 90 91 92 93 94 95 96 97 98 99 100

	return ret;
}

static void at803x_get_wol(struct phy_device *phydev,
			   struct ethtool_wolinfo *wol)
{
	u32 value;

	wol->supported = WAKE_MAGIC;
	wol->wolopts = 0;

	value = phy_read(phydev, AT803X_INTR_ENABLE);
	if (value & AT803X_WOL_ENABLE)
		wol->wolopts |= WAKE_MAGIC;
M
Matus Ujhelyi 已提交
101 102 103 104 105
}

static int at803x_config_init(struct phy_device *phydev)
{
	int val;
106
	int ret;
M
Matus Ujhelyi 已提交
107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140
	u32 features;

	features = SUPPORTED_TP | SUPPORTED_MII | SUPPORTED_AUI |
		   SUPPORTED_FIBRE | SUPPORTED_BNC;

	val = phy_read(phydev, MII_BMSR);
	if (val < 0)
		return val;

	if (val & BMSR_ANEGCAPABLE)
		features |= SUPPORTED_Autoneg;
	if (val & BMSR_100FULL)
		features |= SUPPORTED_100baseT_Full;
	if (val & BMSR_100HALF)
		features |= SUPPORTED_100baseT_Half;
	if (val & BMSR_10FULL)
		features |= SUPPORTED_10baseT_Full;
	if (val & BMSR_10HALF)
		features |= SUPPORTED_10baseT_Half;

	if (val & BMSR_ESTATEN) {
		val = phy_read(phydev, MII_ESTATUS);
		if (val < 0)
			return val;

		if (val & ESTATUS_1000_TFULL)
			features |= SUPPORTED_1000baseT_Full;
		if (val & ESTATUS_1000_THALF)
			features |= SUPPORTED_1000baseT_Half;
	}

	phydev->supported = features;
	phydev->advertising = features;

141 142 143 144 145 146 147 148 149 150 151
	if (phydev->interface == PHY_INTERFACE_MODE_RGMII_TXID) {
		ret = phy_write(phydev, AT803X_DEBUG_ADDR,
				AT803X_DEBUG_SYSTEM_MODE_CTRL);
		if (ret)
			return ret;
		ret = phy_write(phydev, AT803X_DEBUG_DATA,
				AT803X_DEBUG_RGMII_TX_CLK_DLY);
		if (ret)
			return ret;
	}

M
Matus Ujhelyi 已提交
152 153 154
	return 0;
}

155 156 157
static struct phy_driver at803x_driver[] = {
{
	/* ATHEROS 8035 */
M
Matus Ujhelyi 已提交
158 159 160 161
	.phy_id		= 0x004dd072,
	.name		= "Atheros 8035 ethernet",
	.phy_id_mask	= 0xffffffef,
	.config_init	= at803x_config_init,
162 163
	.set_wol	= at803x_set_wol,
	.get_wol	= at803x_get_wol,
M
Matus Ujhelyi 已提交
164 165 166 167 168 169 170
	.features	= PHY_GBIT_FEATURES,
	.flags		= PHY_HAS_INTERRUPT,
	.config_aneg	= &genphy_config_aneg,
	.read_status	= &genphy_read_status,
	.driver		= {
		.owner = THIS_MODULE,
	},
171 172
}, {
	/* ATHEROS 8030 */
M
Matus Ujhelyi 已提交
173 174 175 176
	.phy_id		= 0x004dd076,
	.name		= "Atheros 8030 ethernet",
	.phy_id_mask	= 0xffffffef,
	.config_init	= at803x_config_init,
177 178
	.set_wol	= at803x_set_wol,
	.get_wol	= at803x_get_wol,
M
Matus Ujhelyi 已提交
179 180 181 182 183 184 185
	.features	= PHY_GBIT_FEATURES,
	.flags		= PHY_HAS_INTERRUPT,
	.config_aneg	= &genphy_config_aneg,
	.read_status	= &genphy_read_status,
	.driver		= {
		.owner = THIS_MODULE,
	},
186 187 188 189 190 191 192 193 194 195 196 197 198 199 200
}, {
	/* ATHEROS 8031 */
	.phy_id		= 0x004dd074,
	.name		= "Atheros 8031 ethernet",
	.phy_id_mask	= 0xffffffef,
	.config_init	= at803x_config_init,
	.set_wol	= at803x_set_wol,
	.get_wol	= at803x_get_wol,
	.features	= PHY_GBIT_FEATURES,
	.flags		= PHY_HAS_INTERRUPT,
	.config_aneg	= &genphy_config_aneg,
	.read_status	= &genphy_read_status,
	.driver		= {
		.owner = THIS_MODULE,
	},
201
} };
M
Matus Ujhelyi 已提交
202 203 204

static int __init atheros_init(void)
{
205 206
	return phy_drivers_register(at803x_driver,
				    ARRAY_SIZE(at803x_driver));
M
Matus Ujhelyi 已提交
207 208 209 210
}

static void __exit atheros_exit(void)
{
211 212
	return phy_drivers_unregister(at803x_driver,
				      ARRAY_SIZE(at803x_driver));
M
Matus Ujhelyi 已提交
213 214 215 216 217 218 219
}

module_init(atheros_init);
module_exit(atheros_exit);

static struct mdio_device_id __maybe_unused atheros_tbl[] = {
	{ 0x004dd076, 0xffffffef },
220
	{ 0x004dd074, 0xffffffef },
M
Matus Ujhelyi 已提交
221 222 223 224 225
	{ 0x004dd072, 0xffffffef },
	{ }
};

MODULE_DEVICE_TABLE(mdio, atheros_tbl);