aboutsummaryrefslogtreecommitdiffstats
path: root/drivers/net/phy/national.c
blob: 6c636eb720896c79b0be943e16c1f2b743c10d46 (plain) (blame)
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
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
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
145
146
147
148
149
150
151
152
153
154
155
/*
 * drivers/net/phy/national.c
 *
 * Driver for National Semiconductor PHYs
 *
 * Author: Stuart Menefy <stuart.menefy@st.com>
 * Maintainer: Giuseppe Cavallaro <peppe.cavallaro@st.com>
 *
 * Copyright (c) 2008 STMicroelectronics Limited
 *
 * 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/kernel.h>
#include <linux/module.h>
#include <linux/mii.h>
#include <linux/ethtool.h>
#include <linux/phy.h>
#include <linux/netdevice.h>

/* DP83865 phy identifier values */
#define DP83865_PHY_ID	0x20005c7a

#define DP83865_INT_MASK_REG 0x15
#define DP83865_INT_MASK_STATUS 0x14

#define DP83865_INT_REMOTE_FAULT 0x0008
#define DP83865_INT_ANE_COMPLETED 0x0010
#define DP83865_INT_LINK_CHANGE	0xe000
#define DP83865_INT_MASK_DEFAULT (DP83865_INT_REMOTE_FAULT | \
				DP83865_INT_ANE_COMPLETED | \
				DP83865_INT_LINK_CHANGE)

/* Advanced proprietary configuration */
#define NS_EXP_MEM_CTL	0x16
#define NS_EXP_MEM_DATA	0x1d
#define NS_EXP_MEM_ADD	0x1e

#define LED_CTRL_REG 0x13
#define AN_FALLBACK_AN 0x0001
#define AN_FALLBACK_CRC 0x0002
#define AN_FALLBACK_IE 0x0004
#define ALL_FALLBACK_ON (AN_FALLBACK_AN |  AN_FALLBACK_CRC | AN_FALLBACK_IE)

enum hdx_loopback {
	hdx_loopback_on = 0,
	hdx_loopback_off = 1,
};

static u8 ns_exp_read(struct phy_device *phydev, u16 reg)
{
	phy_write(phydev, NS_EXP_MEM_ADD, reg);
	return phy_read(phydev, NS_EXP_MEM_DATA);
}

static void ns_exp_write(struct phy_device *phydev, u16 reg, u8 data)
{
	phy_write(phydev, NS_EXP_MEM_ADD, reg);
	phy_write(phydev, NS_EXP_MEM_DATA, data);
}

static int ns_config_intr(struct phy_device *phydev)
{
	int err;

	if (phydev->interrupts == PHY_INTERRUPT_ENABLED)
		err = phy_write(phydev, DP83865_INT_MASK_REG,
				DP83865_INT_MASK_DEFAULT);
	else
		err = phy_write(phydev, DP83865_INT_MASK_REG, 0);

	return err;
}

static int ns_ack_interrupt(struct phy_device *phydev)
{
	int ret = phy_read(phydev, DP83865_INT_MASK_STATUS);
	if (ret < 0)
		return ret;

	return 0;
}

static void ns_giga_speed_fallback(struct phy_device *phydev, int mode)
{
	int bmcr = phy_read(phydev, MII_BMCR);

	phy_write(phydev, MII_BMCR, (bmcr | BMCR_PDOWN));

	/* Enable 8 bit expended memory read/write (no auto increment) */
	phy_write(phydev, NS_EXP_MEM_CTL, 0);
	phy_write(phydev, NS_EXP_MEM_ADD, 0x1C0);
	phy_write(phydev, NS_EXP_MEM_DATA, 0x0008);
	phy_write(phydev, MII_BMCR, (bmcr & ~BMCR_PDOWN));
	phy_write(phydev, LED_CTRL_REG, mode);
	return;
}

static void ns_10_base_t_hdx_loopack(struct phy_device *phydev, int disable)
{
	if (disable)
		ns_exp_write(phydev, 0x1c0, ns_exp_read(phydev, 0x1c0) | 1);
	else
		ns_exp_write(phydev, 0x1c0,
			     ns_exp_read(phydev, 0x1c0) & 0xfffe);

	printk(KERN_DEBUG "DP83865 PHY: 10BASE-T HDX loopback %s\n",
	       (ns_exp_read(phydev, 0x1c0) & 0x0001) ? "off" : "on");

	return;
}

static int ns_config_init(struct phy_device *phydev)
{
	ns_giga_speed_fallback(phydev, ALL_FALLBACK_ON);
	/* In the latest MAC or switches design, the 10 Mbps loopback
	   is desired to be turned off. */
	ns_10_base_t_hdx_loopack(phydev, hdx_loopback_off);
	return ns_ack_interrupt(phydev);
}

static struct phy_driver dp83865_driver = {
	.phy_id = DP83865_PHY_ID,
	.phy_id_mask = 0xfffffff0,
	.name = "NatSemi DP83865",
	.features = PHY_GBIT_FEATURES | SUPPORTED_Pause | SUPPORTED_Asym_Pause,
	.flags = PHY_HAS_INTERRUPT,
	.config_init = ns_config_init,
	.config_aneg = genphy_config_aneg,
	.read_status = genphy_read_status,
	.ack_interrupt = ns_ack_interrupt,
	.config_intr = ns_config_intr,
	.driver = {.owner = THIS_MODULE,}
};

static int __init ns_init(void)
{
	return phy_driver_register(&dp83865_driver);
}

static void __exit ns_exit(void)
{
	phy_driver_unregister(&dp83865_driver);
}

MODULE_DESCRIPTION("NatSemi PHY driver");
MODULE_AUTHOR("Stuart Menefy");
MODULE_LICENSE("GPL");

module_init(ns_init);
module_exit(ns_exit);