at803x.c 6.2 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
#define ATH8030_PHY_ID 0x004dd076
#define ATH8031_PHY_ID 0x004dd074
#define ATH8035_PHY_ID 0x004dd072

M
Matus Ujhelyi 已提交
42 43 44 45
MODULE_DESCRIPTION("Atheros 803x PHY driver");
MODULE_AUTHOR("Matus Ujhelyi");
MODULE_LICENSE("GPL");

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

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

65 66
		if (!is_valid_ether_addr(mac))
			return -EFAULT;
M
Matus Ujhelyi 已提交
67

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

		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 已提交
92
	}
93 94 95 96 97 98 99 100 101 102 103 104 105 106 107

	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 已提交
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 145 146 147 148
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 已提交
149 150
static int at803x_config_init(struct phy_device *phydev)
{
151
	int ret;
M
Matus Ujhelyi 已提交
152

153 154 155
	ret = genphy_config_init(phydev);
	if (ret < 0)
		return ret;
M
Matus Ujhelyi 已提交
156

157 158 159 160 161 162 163 164 165 166 167
	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 已提交
168 169 170
	return 0;
}

171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195
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;
}

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

static int __init atheros_init(void)
{
254 255
	return phy_drivers_register(at803x_driver,
				    ARRAY_SIZE(at803x_driver));
M
Matus Ujhelyi 已提交
256 257 258 259
}

static void __exit atheros_exit(void)
{
260
	phy_drivers_unregister(at803x_driver, ARRAY_SIZE(at803x_driver));
M
Matus Ujhelyi 已提交
261 262 263 264 265 266
}

module_init(atheros_init);
module_exit(atheros_exit);

static struct mdio_device_id __maybe_unused atheros_tbl[] = {
267 268 269
	{ ATH8030_PHY_ID, 0xffffffef },
	{ ATH8031_PHY_ID, 0xffffffef },
	{ ATH8035_PHY_ID, 0xffffffef },
M
Matus Ujhelyi 已提交
270 271 272 273
	{ }
};

MODULE_DEVICE_TABLE(mdio, atheros_tbl);