at803x.c 6.1 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
#define AT803X_INER				0x0012
#define AT803X_INER_INIT			0xec00
#define AT803X_INSR				0x0013
33 34 35 36
#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 已提交
37 38 39 40 41

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

42 43
static int at803x_set_wol(struct phy_device *phydev,
			  struct ethtool_wolinfo *wol)
M
Matus Ujhelyi 已提交
44 45 46
{
	struct net_device *ndev = phydev->attached_dev;
	const u8 *mac;
47 48
	int ret;
	u32 value;
M
Matus Ujhelyi 已提交
49 50 51 52 53 54 55
	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)
56
		return -ENODEV;
M
Matus Ujhelyi 已提交
57

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

61 62
		if (!is_valid_ether_addr(mac))
			return -EFAULT;
M
Matus Ujhelyi 已提交
63

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

		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 已提交
88
	}
89 90 91 92 93 94 95 96 97 98 99 100 101 102 103

	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 已提交
104 105
}

106 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 141 142 143 144
static int at803x_suspend(struct phy_device *phydev)
{
	int value;
	int wol_enabled;

	mutex_lock(&phydev->lock);

	value = phy_read(phydev, AT803X_INTR_ENABLE);
	wol_enabled = value & AT803X_WOL_ENABLE;

	value = phy_read(phydev, MII_BMCR);

	if (wol_enabled)
		value |= BMCR_ISOLATE;
	else
		value |= BMCR_PDOWN;

	phy_write(phydev, MII_BMCR, value);

	mutex_unlock(&phydev->lock);

	return 0;
}

static int at803x_resume(struct phy_device *phydev)
{
	int value;

	mutex_lock(&phydev->lock);

	value = phy_read(phydev, MII_BMCR);
	value &= ~(BMCR_PDOWN | BMCR_ISOLATE);
	phy_write(phydev, MII_BMCR, value);

	mutex_unlock(&phydev->lock);

	return 0;
}

M
Matus Ujhelyi 已提交
145 146
static int at803x_config_init(struct phy_device *phydev)
{
147
	int ret;
M
Matus Ujhelyi 已提交
148

149 150 151
	ret = genphy_config_init(phydev);
	if (ret < 0)
		return ret;
M
Matus Ujhelyi 已提交
152

153 154 155 156 157 158 159 160 161 162 163
	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 已提交
164 165 166
	return 0;
}

167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191
static int at803x_ack_interrupt(struct phy_device *phydev)
{
	int err;

	err = phy_read(phydev, AT803X_INSR);

	return (err < 0) ? err : 0;
}

static int at803x_config_intr(struct phy_device *phydev)
{
	int err;
	int value;

	value = phy_read(phydev, AT803X_INER);

	if (phydev->interrupts == PHY_INTERRUPT_ENABLED)
		err = phy_write(phydev, AT803X_INER,
				value | AT803X_INER_INIT);
	else
		err = phy_write(phydev, AT803X_INER, 0);

	return err;
}

192 193 194
static struct phy_driver at803x_driver[] = {
{
	/* ATHEROS 8035 */
M
Matus Ujhelyi 已提交
195 196 197 198
	.phy_id		= 0x004dd072,
	.name		= "Atheros 8035 ethernet",
	.phy_id_mask	= 0xffffffef,
	.config_init	= at803x_config_init,
199 200
	.set_wol	= at803x_set_wol,
	.get_wol	= at803x_get_wol,
201 202
	.suspend	= at803x_suspend,
	.resume		= at803x_resume,
M
Matus Ujhelyi 已提交
203 204
	.features	= PHY_GBIT_FEATURES,
	.flags		= PHY_HAS_INTERRUPT,
205 206
	.config_aneg	= genphy_config_aneg,
	.read_status	= genphy_read_status,
M
Matus Ujhelyi 已提交
207 208 209
	.driver		= {
		.owner = THIS_MODULE,
	},
210 211
}, {
	/* ATHEROS 8030 */
M
Matus Ujhelyi 已提交
212 213 214 215
	.phy_id		= 0x004dd076,
	.name		= "Atheros 8030 ethernet",
	.phy_id_mask	= 0xffffffef,
	.config_init	= at803x_config_init,
216 217
	.set_wol	= at803x_set_wol,
	.get_wol	= at803x_get_wol,
218 219
	.suspend	= at803x_suspend,
	.resume		= at803x_resume,
M
Matus Ujhelyi 已提交
220 221
	.features	= PHY_GBIT_FEATURES,
	.flags		= PHY_HAS_INTERRUPT,
222 223
	.config_aneg	= genphy_config_aneg,
	.read_status	= genphy_read_status,
M
Matus Ujhelyi 已提交
224 225 226
	.driver		= {
		.owner = THIS_MODULE,
	},
227 228 229 230 231 232 233 234
}, {
	/* 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,
235 236
	.suspend	= at803x_suspend,
	.resume		= at803x_resume,
237 238
	.features	= PHY_GBIT_FEATURES,
	.flags		= PHY_HAS_INTERRUPT,
239 240
	.config_aneg	= genphy_config_aneg,
	.read_status	= genphy_read_status,
241 242
	.ack_interrupt  = &at803x_ack_interrupt,
	.config_intr    = &at803x_config_intr,
243 244 245
	.driver		= {
		.owner = THIS_MODULE,
	},
246
} };
M
Matus Ujhelyi 已提交
247 248 249

static int __init atheros_init(void)
{
250 251
	return phy_drivers_register(at803x_driver,
				    ARRAY_SIZE(at803x_driver));
M
Matus Ujhelyi 已提交
252 253 254 255
}

static void __exit atheros_exit(void)
{
256
	phy_drivers_unregister(at803x_driver, ARRAY_SIZE(at803x_driver));
M
Matus Ujhelyi 已提交
257 258 259 260 261 262 263
}

module_init(atheros_init);
module_exit(atheros_exit);

static struct mdio_device_id __maybe_unused atheros_tbl[] = {
	{ 0x004dd076, 0xffffffef },
264
	{ 0x004dd074, 0xffffffef },
M
Matus Ujhelyi 已提交
265 266 267 268 269
	{ 0x004dd072, 0xffffffef },
	{ }
};

MODULE_DEVICE_TABLE(mdio, atheros_tbl);