summaryrefslogtreecommitdiffstats
path: root/drivers/net/phy
diff options
context:
space:
mode:
authorLinus Torvalds <torvalds@linux-foundation.org>2011-10-25 13:25:22 +0200
committerLinus Torvalds <torvalds@linux-foundation.org>2011-10-25 13:25:22 +0200
commit8a9ea3237e7eb5c25f09e429ad242ae5a3d5ea22 (patch)
treea0a63398a9983667d52cbbbf4e2405b4f22b1d83 /drivers/net/phy
parentMerge branch 'usb-next' of git://git.kernel.org/pub/scm/linux/kernel/git/greg... (diff)
parentdp83640: free packet queues on remove (diff)
downloadlinux-8a9ea3237e7eb5c25f09e429ad242ae5a3d5ea22.tar.xz
linux-8a9ea3237e7eb5c25f09e429ad242ae5a3d5ea22.zip
Merge git://git.kernel.org/pub/scm/linux/kernel/git/davem/net-next
* git://git.kernel.org/pub/scm/linux/kernel/git/davem/net-next: (1745 commits) dp83640: free packet queues on remove dp83640: use proper function to free transmit time stamping packets ipv6: Do not use routes from locally generated RAs |PATCH net-next] tg3: add tx_dropped counter be2net: don't create multiple RX/TX rings in multi channel mode be2net: don't create multiple TXQs in BE2 be2net: refactor VF setup/teardown code into be_vf_setup/clear() be2net: add vlan/rx-mode/flow-control config to be_setup() net_sched: cls_flow: use skb_header_pointer() ipv4: avoid useless call of the function check_peer_pmtu TCP: remove TCP_DEBUG net: Fix driver name for mdio-gpio.c ipv4: tcp: fix TOS value in ACK messages sent from TIME_WAIT rtnetlink: Add missing manual netlink notification in dev_change_net_namespaces ipv4: fix ipsec forward performance regression jme: fix irq storm after suspend/resume route: fix ICMP redirect validation net: hold sock reference while processing tx timestamps tcp: md5: add more const attributes Add ethtool -g support to virtio_net ... Fix up conflicts in: - drivers/net/Kconfig: The split-up generated a trivial conflict with removal of a stale reference to Documentation/networking/net-modules.txt. Remove it from the new location instead. - fs/sysfs/dir.c: Fairly nasty conflicts with the sysfs rb-tree usage, conflicting with Eric Biederman's changes for tagged directories.
Diffstat (limited to 'drivers/net/phy')
-rw-r--r--drivers/net/phy/Kconfig2
-rw-r--r--drivers/net/phy/dp83640.c214
-rw-r--r--drivers/net/phy/icplus.c100
-rw-r--r--drivers/net/phy/mdio-gpio.c2
-rw-r--r--drivers/net/phy/phy_device.c4
-rw-r--r--drivers/net/phy/vitesse.c34
6 files changed, 287 insertions, 69 deletions
diff --git a/drivers/net/phy/Kconfig b/drivers/net/phy/Kconfig
index a70244306c94..bb88e12101c7 100644
--- a/drivers/net/phy/Kconfig
+++ b/drivers/net/phy/Kconfig
@@ -3,7 +3,7 @@
#
menuconfig PHYLIB
- tristate "PHY Device support and infrastructure"
+ bool "PHY Device support and infrastructure"
depends on !S390
depends on NETDEVICES
help
diff --git a/drivers/net/phy/dp83640.c b/drivers/net/phy/dp83640.c
index edd7304773eb..9663e0ba6003 100644
--- a/drivers/net/phy/dp83640.c
+++ b/drivers/net/phy/dp83640.c
@@ -35,16 +35,15 @@
#define LAYER4 0x02
#define LAYER2 0x01
#define MAX_RXTS 64
-#define N_EXT_TS 1
+#define N_EXT_TS 6
#define PSF_PTPVER 2
#define PSF_EVNT 0x4000
#define PSF_RX 0x2000
#define PSF_TX 0x1000
#define EXT_EVENT 1
-#define EXT_GPIO 1
-#define CAL_EVENT 2
-#define CAL_GPIO 9
-#define CAL_TRIGGER 2
+#define CAL_EVENT 7
+#define CAL_TRIGGER 7
+#define PER_TRIGGER 6
/* phyter seems to miss the mark by 16 ns */
#define ADJTIME_FIX 16
@@ -131,16 +130,30 @@ struct dp83640_clock {
/* globals */
+enum {
+ CALIBRATE_GPIO,
+ PEROUT_GPIO,
+ EXTTS0_GPIO,
+ EXTTS1_GPIO,
+ EXTTS2_GPIO,
+ EXTTS3_GPIO,
+ EXTTS4_GPIO,
+ EXTTS5_GPIO,
+ GPIO_TABLE_SIZE
+};
+
static int chosen_phy = -1;
-static ushort cal_gpio = 4;
+static ushort gpio_tab[GPIO_TABLE_SIZE] = {
+ 1, 2, 3, 4, 8, 9, 10, 11
+};
module_param(chosen_phy, int, 0444);
-module_param(cal_gpio, ushort, 0444);
+module_param_array(gpio_tab, ushort, NULL, 0444);
MODULE_PARM_DESC(chosen_phy, \
"The address of the PHY to use for the ancillary clock features");
-MODULE_PARM_DESC(cal_gpio, \
- "Which GPIO line to use for synchronizing multiple PHYs");
+MODULE_PARM_DESC(gpio_tab, \
+ "Which GPIO line to use for which purpose: cal,perout,extts1,...,extts6");
/* a list of clocks and a mutex to protect it */
static LIST_HEAD(phyter_clocks);
@@ -235,6 +248,61 @@ static u64 phy2txts(struct phy_txts *p)
return ns;
}
+static void periodic_output(struct dp83640_clock *clock,
+ struct ptp_clock_request *clkreq, bool on)
+{
+ struct dp83640_private *dp83640 = clock->chosen;
+ struct phy_device *phydev = dp83640->phydev;
+ u32 sec, nsec, period;
+ u16 gpio, ptp_trig, trigger, val;
+
+ gpio = on ? gpio_tab[PEROUT_GPIO] : 0;
+ trigger = PER_TRIGGER;
+
+ ptp_trig = TRIG_WR |
+ (trigger & TRIG_CSEL_MASK) << TRIG_CSEL_SHIFT |
+ (gpio & TRIG_GPIO_MASK) << TRIG_GPIO_SHIFT |
+ TRIG_PER |
+ TRIG_PULSE;
+
+ val = (trigger & TRIG_SEL_MASK) << TRIG_SEL_SHIFT;
+
+ if (!on) {
+ val |= TRIG_DIS;
+ mutex_lock(&clock->extreg_lock);
+ ext_write(0, phydev, PAGE5, PTP_TRIG, ptp_trig);
+ ext_write(0, phydev, PAGE4, PTP_CTL, val);
+ mutex_unlock(&clock->extreg_lock);
+ return;
+ }
+
+ sec = clkreq->perout.start.sec;
+ nsec = clkreq->perout.start.nsec;
+ period = clkreq->perout.period.sec * 1000000000UL;
+ period += clkreq->perout.period.nsec;
+
+ mutex_lock(&clock->extreg_lock);
+
+ ext_write(0, phydev, PAGE5, PTP_TRIG, ptp_trig);
+
+ /*load trigger*/
+ val |= TRIG_LOAD;
+ ext_write(0, phydev, PAGE4, PTP_CTL, val);
+ ext_write(0, phydev, PAGE4, PTP_TDR, nsec & 0xffff); /* ns[15:0] */
+ ext_write(0, phydev, PAGE4, PTP_TDR, nsec >> 16); /* ns[31:16] */
+ ext_write(0, phydev, PAGE4, PTP_TDR, sec & 0xffff); /* sec[15:0] */
+ ext_write(0, phydev, PAGE4, PTP_TDR, sec >> 16); /* sec[31:16] */
+ ext_write(0, phydev, PAGE4, PTP_TDR, period & 0xffff); /* ns[15:0] */
+ ext_write(0, phydev, PAGE4, PTP_TDR, period >> 16); /* ns[31:16] */
+
+ /*enable trigger*/
+ val &= ~TRIG_LOAD;
+ val |= TRIG_EN;
+ ext_write(0, phydev, PAGE4, PTP_CTL, val);
+
+ mutex_unlock(&clock->extreg_lock);
+}
+
/* ptp clock methods */
static int ptp_dp83640_adjfreq(struct ptp_clock_info *ptp, s32 ppb)
@@ -338,19 +406,30 @@ static int ptp_dp83640_enable(struct ptp_clock_info *ptp,
struct dp83640_clock *clock =
container_of(ptp, struct dp83640_clock, caps);
struct phy_device *phydev = clock->chosen->phydev;
- u16 evnt;
+ int index;
+ u16 evnt, event_num, gpio_num;
switch (rq->type) {
case PTP_CLK_REQ_EXTTS:
- if (rq->extts.index != 0)
+ index = rq->extts.index;
+ if (index < 0 || index >= N_EXT_TS)
return -EINVAL;
- evnt = EVNT_WR | (EXT_EVENT & EVNT_SEL_MASK) << EVNT_SEL_SHIFT;
+ event_num = EXT_EVENT + index;
+ evnt = EVNT_WR | (event_num & EVNT_SEL_MASK) << EVNT_SEL_SHIFT;
if (on) {
- evnt |= (EXT_GPIO & EVNT_GPIO_MASK) << EVNT_GPIO_SHIFT;
+ gpio_num = gpio_tab[EXTTS0_GPIO + index];
+ evnt |= (gpio_num & EVNT_GPIO_MASK) << EVNT_GPIO_SHIFT;
evnt |= EVNT_RISE;
}
ext_write(0, phydev, PAGE5, PTP_EVNT, evnt);
return 0;
+
+ case PTP_CLK_REQ_PEROUT:
+ if (rq->perout.index != 0)
+ return -EINVAL;
+ periodic_output(clock, rq, on);
+ return 0;
+
default:
break;
}
@@ -441,9 +520,10 @@ static void recalibrate(struct dp83640_clock *clock)
struct list_head *this;
struct dp83640_private *tmp;
struct phy_device *master = clock->chosen->phydev;
- u16 cfg0, evnt, ptp_trig, trigger, val;
+ u16 cal_gpio, cfg0, evnt, ptp_trig, trigger, val;
trigger = CAL_TRIGGER;
+ cal_gpio = gpio_tab[CALIBRATE_GPIO];
mutex_lock(&clock->extreg_lock);
@@ -542,11 +622,17 @@ static void recalibrate(struct dp83640_clock *clock)
/* time stamping methods */
+static inline u16 exts_chan_to_edata(int ch)
+{
+ return 1 << ((ch + EXT_EVENT) * 2);
+}
+
static int decode_evnt(struct dp83640_private *dp83640,
void *data, u16 ests)
{
struct phy_txts *phy_txts;
struct ptp_clock_event event;
+ int i, parsed;
int words = (ests >> EVNT_TS_LEN_SHIFT) & EVNT_TS_LEN_MASK;
u16 ext_status = 0;
@@ -568,14 +654,25 @@ static int decode_evnt(struct dp83640_private *dp83640,
dp83640->edata.ns_lo = phy_txts->ns_lo;
}
+ if (ext_status) {
+ parsed = words + 2;
+ } else {
+ parsed = words + 1;
+ i = ((ests >> EVNT_NUM_SHIFT) & EVNT_NUM_MASK) - EXT_EVENT;
+ ext_status = exts_chan_to_edata(i);
+ }
+
event.type = PTP_CLOCK_EXTTS;
- event.index = 0;
event.timestamp = phy2txts(&dp83640->edata);
- ptp_clock_event(dp83640->clock->ptp_clock, &event);
+ for (i = 0; i < N_EXT_TS; i++) {
+ if (ext_status & exts_chan_to_edata(i)) {
+ event.index = i;
+ ptp_clock_event(dp83640->clock->ptp_clock, &event);
+ }
+ }
- words = ext_status ? words + 2 : words + 1;
- return words * sizeof(u16);
+ return parsed * sizeof(u16);
}
static void decode_rxts(struct dp83640_private *dp83640,
@@ -664,6 +761,41 @@ static void decode_status_frame(struct dp83640_private *dp83640,
}
}
+static int is_sync(struct sk_buff *skb, int type)
+{
+ u8 *data = skb->data, *msgtype;
+ unsigned int offset = 0;
+
+ switch (type) {
+ case PTP_CLASS_V1_IPV4:
+ case PTP_CLASS_V2_IPV4:
+ offset = ETH_HLEN + IPV4_HLEN(data) + UDP_HLEN;
+ break;
+ case PTP_CLASS_V1_IPV6:
+ case PTP_CLASS_V2_IPV6:
+ offset = OFF_PTP6;
+ break;
+ case PTP_CLASS_V2_L2:
+ offset = ETH_HLEN;
+ break;
+ case PTP_CLASS_V2_VLAN:
+ offset = ETH_HLEN + VLAN_HLEN;
+ break;
+ default:
+ return 0;
+ }
+
+ if (type & PTP_CLASS_V1)
+ offset += OFF_PTP_CONTROL;
+
+ if (skb->len < offset + 1)
+ return 0;
+
+ msgtype = data + offset;
+
+ return (*msgtype & 0xf) == 0;
+}
+
static int match(struct sk_buff *skb, unsigned int type, struct rxts *rxts)
{
u16 *seqid;
@@ -740,7 +872,7 @@ static void dp83640_clock_init(struct dp83640_clock *clock, struct mii_bus *bus)
clock->caps.max_adj = 1953124;
clock->caps.n_alarm = 0;
clock->caps.n_ext_ts = N_EXT_TS;
- clock->caps.n_per_out = 0;
+ clock->caps.n_per_out = 1;
clock->caps.pps = 0;
clock->caps.adjfreq = ptp_dp83640_adjfreq;
clock->caps.adjtime = ptp_dp83640_adjtime;
@@ -875,6 +1007,7 @@ static void dp83640_remove(struct phy_device *phydev)
struct dp83640_clock *clock;
struct list_head *this, *next;
struct dp83640_private *tmp, *dp83640 = phydev->priv;
+ struct sk_buff *skb;
if (phydev->addr == BROADCAST_ADDR)
return;
@@ -882,6 +1015,12 @@ static void dp83640_remove(struct phy_device *phydev)
enable_status_frames(phydev, false);
cancel_work_sync(&dp83640->ts_work);
+ while ((skb = skb_dequeue(&dp83640->rx_queue)) != NULL)
+ kfree_skb(skb);
+
+ while ((skb = skb_dequeue(&dp83640->tx_queue)) != NULL)
+ skb_complete_tx_timestamp(skb, NULL);
+
clock = dp83640_clock_get(dp83640->clock);
if (dp83640 == clock->chosen) {
@@ -913,16 +1052,10 @@ static int dp83640_hwtstamp(struct phy_device *phydev, struct ifreq *ifr)
if (cfg.flags) /* reserved for future extensions */
return -EINVAL;
- switch (cfg.tx_type) {
- case HWTSTAMP_TX_OFF:
- dp83640->hwts_tx_en = 0;
- break;
- case HWTSTAMP_TX_ON:
- dp83640->hwts_tx_en = 1;
- break;
- default:
+ if (cfg.tx_type < 0 || cfg.tx_type > HWTSTAMP_TX_ONESTEP_SYNC)
return -ERANGE;
- }
+
+ dp83640->hwts_tx_en = cfg.tx_type;
switch (cfg.rx_filter) {
case HWTSTAMP_FILTER_NONE:
@@ -977,6 +1110,9 @@ static int dp83640_hwtstamp(struct phy_device *phydev, struct ifreq *ifr)
if (dp83640->hwts_tx_en)
txcfg0 |= TX_TS_EN;
+ if (dp83640->hwts_tx_en == HWTSTAMP_TX_ONESTEP_SYNC)
+ txcfg0 |= SYNC_1STEP | CHK_1STEP;
+
if (dp83640->hwts_rx_en)
rxcfg0 |= RX_TS_EN;
@@ -1059,12 +1195,24 @@ static void dp83640_txtstamp(struct phy_device *phydev,
{
struct dp83640_private *dp83640 = phydev->priv;
- if (!dp83640->hwts_tx_en) {
- kfree_skb(skb);
- return;
+ switch (dp83640->hwts_tx_en) {
+
+ case HWTSTAMP_TX_ONESTEP_SYNC:
+ if (is_sync(skb, type)) {
+ skb_complete_tx_timestamp(skb, NULL);
+ return;
+ }
+ /* fall through */
+ case HWTSTAMP_TX_ON:
+ skb_queue_tail(&dp83640->tx_queue, skb);
+ schedule_work(&dp83640->ts_work);
+ break;
+
+ case HWTSTAMP_TX_OFF:
+ default:
+ skb_complete_tx_timestamp(skb, NULL);
+ break;
}
- skb_queue_tail(&dp83640->tx_queue, skb);
- schedule_work(&dp83640->ts_work);
}
static struct phy_driver dp83640_driver = {
diff --git a/drivers/net/phy/icplus.c b/drivers/net/phy/icplus.c
index d4cbc2922b23..c81f136ae670 100644
--- a/drivers/net/phy/icplus.c
+++ b/drivers/net/phy/icplus.c
@@ -30,10 +30,17 @@
#include <asm/irq.h>
#include <asm/uaccess.h>
-MODULE_DESCRIPTION("ICPlus IP175C/IC1001 PHY drivers");
+MODULE_DESCRIPTION("ICPlus IP175C/IP101A/IC1001 PHY drivers");
MODULE_AUTHOR("Michael Barkowski");
MODULE_LICENSE("GPL");
+/* IP101A/IP1001 */
+#define IP10XX_SPEC_CTRL_STATUS 16 /* Spec. Control Register */
+#define IP1001_SPEC_CTRL_STATUS_2 20 /* IP1001 Spec. Control Reg 2 */
+#define IP1001_PHASE_SEL_MASK 3 /* IP1001 RX/TXPHASE_SEL */
+#define IP1001_APS_ON 11 /* IP1001 APS Mode bit */
+#define IP101A_APS_ON 2 /* IP101A APS Mode bit */
+
static int ip175c_config_init(struct phy_device *phydev)
{
int err, i;
@@ -42,36 +49,36 @@ static int ip175c_config_init(struct phy_device *phydev)
if (full_reset_performed == 0) {
/* master reset */
- err = phydev->bus->write(phydev->bus, 30, 0, 0x175c);
+ err = mdiobus_write(phydev->bus, 30, 0, 0x175c);
if (err < 0)
return err;
/* ensure no bus delays overlap reset period */
- err = phydev->bus->read(phydev->bus, 30, 0);
+ err = mdiobus_read(phydev->bus, 30, 0);
/* data sheet specifies reset period is 2 msec */
mdelay(2);
/* enable IP175C mode */
- err = phydev->bus->write(phydev->bus, 29, 31, 0x175c);
+ err = mdiobus_write(phydev->bus, 29, 31, 0x175c);
if (err < 0)
return err;
/* Set MII0 speed and duplex (in PHY mode) */
- err = phydev->bus->write(phydev->bus, 29, 22, 0x420);
+ err = mdiobus_write(phydev->bus, 29, 22, 0x420);
if (err < 0)
return err;
/* reset switch ports */
for (i = 0; i < 5; i++) {
- err = phydev->bus->write(phydev->bus, i,
- MII_BMCR, BMCR_RESET);
+ err = mdiobus_write(phydev->bus, i,
+ MII_BMCR, BMCR_RESET);
if (err < 0)
return err;
}
for (i = 0; i < 5; i++)
- err = phydev->bus->read(phydev->bus, i, MII_BMCR);
+ err = mdiobus_read(phydev->bus, i, MII_BMCR);
mdelay(2);
@@ -89,27 +96,61 @@ static int ip175c_config_init(struct phy_device *phydev)
return 0;
}
-static int ip1001_config_init(struct phy_device *phydev)
+static int ip1xx_reset(struct phy_device *phydev)
{
- int err, value;
+ int err, bmcr;
/* Software Reset PHY */
- value = phy_read(phydev, MII_BMCR);
- value |= BMCR_RESET;
- err = phy_write(phydev, MII_BMCR, value);
+ bmcr = phy_read(phydev, MII_BMCR);
+ bmcr |= BMCR_RESET;
+ err = phy_write(phydev, MII_BMCR, bmcr);
if (err < 0)
return err;
do {
- value = phy_read(phydev, MII_BMCR);
- } while (value & BMCR_RESET);
+ bmcr = phy_read(phydev, MII_BMCR);
+ } while (bmcr & BMCR_RESET);
+
+ return err;
+}
+
+static int ip1001_config_init(struct phy_device *phydev)
+{
+ int c;
+
+ c = ip1xx_reset(phydev);
+ if (c < 0)
+ return c;
+
+ /* Enable Auto Power Saving mode */
+ c = phy_read(phydev, IP1001_SPEC_CTRL_STATUS_2);
+ c |= IP1001_APS_ON;
+ if (c < 0)
+ return c;
+
+ if (phydev->interface == PHY_INTERFACE_MODE_RGMII) {
+ /* Additional delay (2ns) used to adjust RX clock phase
+ * at RGMII interface */
+ c = phy_read(phydev, IP10XX_SPEC_CTRL_STATUS);
+ c |= IP1001_PHASE_SEL_MASK;
+ c = phy_write(phydev, IP10XX_SPEC_CTRL_STATUS, c);
+ }
+
+ return c;
+}
+
+static int ip101a_config_init(struct phy_device *phydev)
+{
+ int c;
- /* Additional delay (2ns) used to adjust RX clock phase
- * at GMII/ RGMII interface */
- value = phy_read(phydev, 16);
- value |= 0x3;
+ c = ip1xx_reset(phydev);
+ if (c < 0)
+ return c;
- return phy_write(phydev, 16, value);
+ /* Enable Auto Power Saving mode */
+ c = phy_read(phydev, IP10XX_SPEC_CTRL_STATUS);
+ c |= IP101A_APS_ON;
+ return c;
}
static int ip175c_read_status(struct phy_device *phydev)
@@ -158,6 +199,20 @@ static struct phy_driver ip1001_driver = {
.driver = { .owner = THIS_MODULE,},
};
+static struct phy_driver ip101a_driver = {
+ .phy_id = 0x02430c54,
+ .name = "ICPlus IP101A",
+ .phy_id_mask = 0x0ffffff0,
+ .features = PHY_BASIC_FEATURES | SUPPORTED_Pause |
+ SUPPORTED_Asym_Pause,
+ .config_init = &ip101a_config_init,
+ .config_aneg = &genphy_config_aneg,
+ .read_status = &genphy_read_status,
+ .suspend = genphy_suspend,
+ .resume = genphy_resume,
+ .driver = { .owner = THIS_MODULE,},
+};
+
static int __init icplus_init(void)
{
int ret = 0;
@@ -166,12 +221,17 @@ static int __init icplus_init(void)
if (ret < 0)
return -ENODEV;
+ ret = phy_driver_register(&ip101a_driver);
+ if (ret < 0)
+ return -ENODEV;
+
return phy_driver_register(&ip175c_driver);
}
static void __exit icplus_exit(void)
{
phy_driver_unregister(&ip1001_driver);
+ phy_driver_unregister(&ip101a_driver);
phy_driver_unregister(&ip175c_driver);
}
diff --git a/drivers/net/phy/mdio-gpio.c b/drivers/net/phy/mdio-gpio.c
index 47c8339a0359..2843c90f712f 100644
--- a/drivers/net/phy/mdio-gpio.c
+++ b/drivers/net/phy/mdio-gpio.c
@@ -241,7 +241,7 @@ MODULE_DEVICE_TABLE(of, mdio_ofgpio_match);
static struct platform_driver mdio_ofgpio_driver = {
.driver = {
- .name = "mdio-gpio",
+ .name = "mdio-ofgpio",
.owner = THIS_MODULE,
.of_match_table = mdio_ofgpio_match,
},
diff --git a/drivers/net/phy/phy_device.c b/drivers/net/phy/phy_device.c
index ff109fe5af6b..83a5a5afec67 100644
--- a/drivers/net/phy/phy_device.c
+++ b/drivers/net/phy/phy_device.c
@@ -213,7 +213,7 @@ int get_phy_id(struct mii_bus *bus, int addr, u32 *phy_id)
/* Grab the bits from PHYIR1, and put them
* in the upper half */
- phy_reg = bus->read(bus, addr, MII_PHYSID1);
+ phy_reg = mdiobus_read(bus, addr, MII_PHYSID1);
if (phy_reg < 0)
return -EIO;
@@ -221,7 +221,7 @@ int get_phy_id(struct mii_bus *bus, int addr, u32 *phy_id)
*phy_id = (phy_reg & 0xffff) << 16;
/* Grab the bits from PHYIR2, and put them in the lower half */
- phy_reg = bus->read(bus, addr, MII_PHYSID2);
+ phy_reg = mdiobus_read(bus, addr, MII_PHYSID2);
if (phy_reg < 0)
return -EIO;
diff --git a/drivers/net/phy/vitesse.c b/drivers/net/phy/vitesse.c
index 5d8f6e17bd55..0ec8e09cc2ac 100644
--- a/drivers/net/phy/vitesse.c
+++ b/drivers/net/phy/vitesse.c
@@ -3,7 +3,7 @@
*
* Author: Kriston Carson
*
- * Copyright (c) 2005 Freescale Semiconductor, Inc.
+ * Copyright (c) 2005, 2009 Freescale Semiconductor, Inc.
*
* 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
@@ -61,32 +61,42 @@ MODULE_DESCRIPTION("Vitesse PHY driver");
MODULE_AUTHOR("Kriston Carson");
MODULE_LICENSE("GPL");
-static int vsc824x_config_init(struct phy_device *phydev)
+int vsc824x_add_skew(struct phy_device *phydev)
{
- int extcon;
int err;
-
- err = phy_write(phydev, MII_VSC8244_AUX_CONSTAT,
- MII_VSC8244_AUXCONSTAT_INIT);
- if (err < 0)
- return err;
+ int extcon;
extcon = phy_read(phydev, MII_VSC8244_EXT_CON1);
if (extcon < 0)
- return err;
+ return extcon;
extcon &= ~(MII_VSC8244_EXTCON1_TX_SKEW_MASK |
MII_VSC8244_EXTCON1_RX_SKEW_MASK);
- if (phydev->interface == PHY_INTERFACE_MODE_RGMII_ID)
- extcon |= (MII_VSC8244_EXTCON1_TX_SKEW |
- MII_VSC8244_EXTCON1_RX_SKEW);
+ extcon |= (MII_VSC8244_EXTCON1_TX_SKEW |
+ MII_VSC8244_EXTCON1_RX_SKEW);
err = phy_write(phydev, MII_VSC8244_EXT_CON1, extcon);
return err;
}
+EXPORT_SYMBOL(vsc824x_add_skew);
+
+static int vsc824x_config_init(struct phy_device *phydev)
+{
+ int err;
+
+ err = phy_write(phydev, MII_VSC8244_AUX_CONSTAT,
+ MII_VSC8244_AUXCONSTAT_INIT);
+ if (err < 0)
+ return err;
+
+ if (phydev->interface == PHY_INTERFACE_MODE_RGMII_ID)
+ err = vsc824x_add_skew(phydev);
+
+ return err;
+}
static int vsc824x_ack_interrupt(struct phy_device *phydev)
{