mirror of
				git://git.openwrt.org/openwrt/openwrt.git
				synced 2025-10-31 14:04:26 -04:00 
			
		
		
		
	All patches automatically rebased.
Build system: x86_64
Build-tested: ramips/mt7621*
*Had to revert 7f1edbd in order to build due to FS#4149
Signed-off-by: John Audia <graysky@archlinux.us>
		
	
			
		
			
				
	
	
		
			621 lines
		
	
	
		
			15 KiB
		
	
	
	
		
			Diff
		
	
	
	
	
	
			
		
		
	
	
			621 lines
		
	
	
		
			15 KiB
		
	
	
	
		
			Diff
		
	
	
	
	
	
| From 630ee8f358d961a7c8295d60a112e27cbfe4478d Mon Sep 17 00:00:00 2001
 | |
| From: Florin Chiculita <florinlaurentiu.chiculita@nxp.com>
 | |
| Date: Fri, 9 Nov 2018 06:20:36 +0200
 | |
| Subject: [PATCH] net/phy: Inphi IN112525_s03 retimer support
 | |
| 
 | |
| Software controller for IN112525_s03 retimer
 | |
| 
 | |
| Signed-off-by: Florin Chiculita <florinlaurentiu.chiculita@nxp.com>
 | |
| ---
 | |
|  drivers/net/phy/Kconfig  |   5 +
 | |
|  drivers/net/phy/Makefile |   1 +
 | |
|  drivers/net/phy/inphi.c  | 578 +++++++++++++++++++++++++++++++++++++++++++++++
 | |
|  3 files changed, 584 insertions(+)
 | |
|  create mode 100644 drivers/net/phy/inphi.c
 | |
| 
 | |
| --- a/drivers/net/phy/Kconfig
 | |
| +++ b/drivers/net/phy/Kconfig
 | |
| @@ -471,6 +471,11 @@ config ICPLUS_PHY
 | |
|  	---help---
 | |
|  	  Currently supports the IP175C and IP1001 PHYs.
 | |
|  
 | |
| +config INPHI_PHY
 | |
| +	tristate "Inphi CDR 10G/25G Ethernet PHY"
 | |
| +	---help---
 | |
| +	  Currently supports the IN112525_S03 part @ 25G
 | |
| +
 | |
|  config INTEL_XWAY_PHY
 | |
|  	tristate "Intel XWAY PHYs"
 | |
|  	---help---
 | |
| --- a/drivers/net/phy/Makefile
 | |
| +++ b/drivers/net/phy/Makefile
 | |
| @@ -86,6 +86,7 @@ obj-$(CONFIG_DP83848_PHY)	+= dp83848.o
 | |
|  obj-$(CONFIG_DP83867_PHY)	+= dp83867.o
 | |
|  obj-$(CONFIG_FIXED_PHY)		+= fixed_phy.o
 | |
|  obj-$(CONFIG_ICPLUS_PHY)	+= icplus.o
 | |
| +obj-$(CONFIG_INPHI_PHY)	+= inphi.o
 | |
|  obj-$(CONFIG_INTEL_XWAY_PHY)	+= intel-xway.o
 | |
|  obj-$(CONFIG_LSI_ET1011C_PHY)	+= et1011c.o
 | |
|  obj-$(CONFIG_LXT_PHY)		+= lxt.o
 | |
| --- /dev/null
 | |
| +++ b/drivers/net/phy/inphi.c
 | |
| @@ -0,0 +1,578 @@
 | |
| +/*
 | |
| + * Copyright 2018 NXP
 | |
| + * Copyright 2018 INPHI
 | |
| + *
 | |
| + * Redistribution and use in source and binary forms, with or without
 | |
| + * modification, are permitted provided that the following conditions are met:
 | |
| + *
 | |
| + * 1. Redistributions of source code must retain the above copyright notice,
 | |
| + * this list of conditions and the following disclaimer.
 | |
| + * 2. Redistributions in binary form must reproduce the above copyright notice,
 | |
| + * this list of conditions and the following disclaimer in the documentation
 | |
| + * and/or other materials provided with the distribution.
 | |
| + *
 | |
| + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
 | |
| + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 | |
| + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 | |
| + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
 | |
| + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
 | |
| + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
 | |
| + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
 | |
| + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
 | |
| + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
 | |
| + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 | |
| + * POSSIBILITY OF SUCH DAMAGE.
 | |
| + *
 | |
| + * Inphi is a registered trademark of Inphi Corporation
 | |
| + *
 | |
| + */
 | |
| +
 | |
| +#include <linux/module.h>
 | |
| +#include <linux/phy.h>
 | |
| +#include <linux/mdio.h>
 | |
| +#include <linux/interrupt.h>
 | |
| +#include <linux/platform_device.h>
 | |
| +#include <linux/of_irq.h>
 | |
| +#include <linux/workqueue.h>
 | |
| +#include <linux/i2c.h>
 | |
| +#include <linux/timer.h>
 | |
| +#include <linux/delay.h>
 | |
| +#include <linux/kernel.h>
 | |
| +#include <linux/init.h>
 | |
| +#include <linux/fs.h>
 | |
| +#include <linux/cdev.h>
 | |
| +#include <linux/device.h>
 | |
| +#include <linux/slab.h>
 | |
| +#include <linux/uaccess.h>
 | |
| +
 | |
| +#define PHY_ID_IN112525  0x02107440
 | |
| +
 | |
| +#define INPHI_S03_DEVICE_ID_MSB 0x2
 | |
| +#define INPHI_S03_DEVICE_ID_LSB 0x3
 | |
| +
 | |
| +#define ALL_LANES		4
 | |
| +#define INPHI_POLL_DELAY	2500
 | |
| +
 | |
| +#define PHYCTRL_REG1	0x0012
 | |
| +#define PHYCTRL_REG2	0x0014
 | |
| +#define PHYCTRL_REG3	0x0120
 | |
| +#define PHYCTRL_REG4	0x0121
 | |
| +#define PHYCTRL_REG5	0x0180
 | |
| +#define PHYCTRL_REG6	0x0580
 | |
| +#define PHYCTRL_REG7	0x05C4
 | |
| +#define PHYCTRL_REG8	0x01C8
 | |
| +#define PHYCTRL_REG9	0x0521
 | |
| +
 | |
| +#define PHYSTAT_REG1	0x0021
 | |
| +#define PHYSTAT_REG2	0x0022
 | |
| +#define PHYSTAT_REG3	0x0123
 | |
| +
 | |
| +#define PHYMISC_REG1	0x0025
 | |
| +#define PHYMISC_REG2	0x002c
 | |
| +#define PHYMISC_REG3	0x00b3
 | |
| +#define PHYMISC_REG4	0x0181
 | |
| +#define PHYMISC_REG5	0x019D
 | |
| +#define PHYMISC_REG6	0x0198
 | |
| +#define PHYMISC_REG7	0x0199
 | |
| +#define PHYMISC_REG8	0x0581
 | |
| +#define PHYMISC_REG9	0x0598
 | |
| +#define PHYMISC_REG10	0x059c
 | |
| +#define PHYMISC_REG20	0x01B0
 | |
| +#define PHYMISC_REG21	0x01BC
 | |
| +#define PHYMISC_REG22	0x01C0
 | |
| +
 | |
| +#define RX_VCO_CODE_OFFSET	5
 | |
| +
 | |
| +#define mdio_wr(a, b)	phy_write_mmd(inphi_phydev, MDIO_MMD_VEND1, (a), (b))
 | |
| +#define mdio_rd(a)	phy_read_mmd(inphi_phydev, MDIO_MMD_VEND1, (a))
 | |
| +
 | |
| +#define VCO_CODE  390
 | |
| +
 | |
| +int vco_codes[ALL_LANES] = {
 | |
| +	VCO_CODE,
 | |
| +	VCO_CODE,
 | |
| +	VCO_CODE,
 | |
| +	VCO_CODE
 | |
| +};
 | |
| +
 | |
| +static void mykmod_work_handler(struct work_struct *w);
 | |
| +
 | |
| +static struct workqueue_struct *wq;
 | |
| +static DECLARE_DELAYED_WORK(mykmod_work, mykmod_work_handler);
 | |
| +static unsigned long onesec;
 | |
| +struct phy_device *inphi_phydev;
 | |
| +
 | |
| +int bit_test(int value, int bit_field)
 | |
| +{
 | |
| +	int result;
 | |
| +	int bit_mask = (1 << bit_field);
 | |
| +
 | |
| +	result = ((value & bit_mask) == bit_mask);
 | |
| +	return result;
 | |
| +}
 | |
| +
 | |
| +int tx_pll_lock_test(int lane)
 | |
| +{
 | |
| +	int i, val, locked = 1;
 | |
| +
 | |
| +	if (lane == ALL_LANES) {
 | |
| +		for (i = 0; i < ALL_LANES; i++) {
 | |
| +			val = mdio_rd(i * 0x100 + PHYSTAT_REG3);
 | |
| +			locked = locked & bit_test(val, 15);
 | |
| +		}
 | |
| +	} else {
 | |
| +		val = mdio_rd(lane * 0x100 + PHYSTAT_REG3);
 | |
| +		locked = locked & bit_test(val, 15);
 | |
| +	}
 | |
| +
 | |
| +	return locked;
 | |
| +}
 | |
| +
 | |
| +void rx_reset_assert(int lane)
 | |
| +{
 | |
| +	int mask, val;
 | |
| +
 | |
| +	if (lane == ALL_LANES) {
 | |
| +		val = mdio_rd(PHYMISC_REG2);
 | |
| +		mask = (1 << 15);
 | |
| +		mdio_wr(PHYMISC_REG2, val + mask);
 | |
| +	} else {
 | |
| +		val = mdio_rd(lane * 0x100 + PHYCTRL_REG8);
 | |
| +		mask = (1 << 6);
 | |
| +		mdio_wr(lane * 0x100 + PHYCTRL_REG8, val + mask);
 | |
| +	}
 | |
| +}
 | |
| +
 | |
| +void rx_reset_de_assert(int lane)
 | |
| +{
 | |
| +	int mask, val;
 | |
| +
 | |
| +	if (lane == ALL_LANES) {
 | |
| +		val = mdio_rd(PHYMISC_REG2);
 | |
| +		mask = 0xffff - (1 << 15);
 | |
| +		mdio_wr(PHYMISC_REG2, val & mask);
 | |
| +	} else {
 | |
| +		val = mdio_rd(lane * 0x100 + PHYCTRL_REG8);
 | |
| +		mask = 0xffff - (1 << 6);
 | |
| +		mdio_wr(lane * 0x100 + PHYCTRL_REG8, val & mask);
 | |
| +	}
 | |
| +}
 | |
| +
 | |
| +void rx_powerdown_assert(int lane)
 | |
| +{
 | |
| +	int mask, val;
 | |
| +
 | |
| +	val = mdio_rd(lane * 0x100 + PHYCTRL_REG8);
 | |
| +	mask = (1 << 5);
 | |
| +	mdio_wr(lane * 0x100 + PHYCTRL_REG8, val + mask);
 | |
| +}
 | |
| +
 | |
| +void rx_powerdown_de_assert(int lane)
 | |
| +{
 | |
| +	int mask, val;
 | |
| +
 | |
| +	val = mdio_rd(lane * 0x100 + PHYCTRL_REG8);
 | |
| +	mask = 0xffff - (1 << 5);
 | |
| +	mdio_wr(lane * 0x100 + PHYCTRL_REG8, val & mask);
 | |
| +}
 | |
| +
 | |
| +void tx_pll_assert(int lane)
 | |
| +{
 | |
| +	int val, recal;
 | |
| +
 | |
| +	if (lane == ALL_LANES) {
 | |
| +		val = mdio_rd(PHYMISC_REG2);
 | |
| +		recal = (1 << 12);
 | |
| +		mdio_wr(PHYMISC_REG2, val | recal);
 | |
| +	} else {
 | |
| +		val = mdio_rd(lane * 0x100 + PHYCTRL_REG4);
 | |
| +		recal = (1 << 15);
 | |
| +		mdio_wr(lane * 0x100 + PHYCTRL_REG4, val | recal);
 | |
| +	}
 | |
| +}
 | |
| +
 | |
| +void tx_pll_de_assert(int lane)
 | |
| +{
 | |
| +	int recal, val;
 | |
| +
 | |
| +	if (lane == ALL_LANES) {
 | |
| +		val = mdio_rd(PHYMISC_REG2);
 | |
| +		recal = 0xefff;
 | |
| +		mdio_wr(PHYMISC_REG2, val & recal);
 | |
| +	} else {
 | |
| +		val = mdio_rd(lane * 0x100 + PHYCTRL_REG4);
 | |
| +		recal = 0x7fff;
 | |
| +		mdio_wr(lane * 0x100 + PHYCTRL_REG4, val & recal);
 | |
| +	}
 | |
| +}
 | |
| +
 | |
| +void tx_core_assert(int lane)
 | |
| +{
 | |
| +	int recal, val, val2, core_reset;
 | |
| +
 | |
| +	if (lane == 4) {
 | |
| +		val = mdio_rd(PHYMISC_REG2);
 | |
| +		recal = 1 << 10;
 | |
| +		mdio_wr(PHYMISC_REG2, val | recal);
 | |
| +	} else {
 | |
| +		val2 = mdio_rd(PHYMISC_REG3);
 | |
| +		core_reset = (1 << (lane + 8));
 | |
| +		mdio_wr(PHYMISC_REG3, val2 | core_reset);
 | |
| +	}
 | |
| +}
 | |
| +
 | |
| +void lol_disable(int lane)
 | |
| +{
 | |
| +	int val, mask;
 | |
| +
 | |
| +	val = mdio_rd(PHYMISC_REG3);
 | |
| +	mask = 1 << (lane + 4);
 | |
| +	mdio_wr(PHYMISC_REG3, val | mask);
 | |
| +}
 | |
| +
 | |
| +void tx_core_de_assert(int lane)
 | |
| +{
 | |
| +	int val, recal, val2, core_reset;
 | |
| +
 | |
| +	if (lane == ALL_LANES) {
 | |
| +		val = mdio_rd(PHYMISC_REG2);
 | |
| +		recal = 0xffff - (1 << 10);
 | |
| +		mdio_wr(PHYMISC_REG2, val & recal);
 | |
| +	} else {
 | |
| +		val2 = mdio_rd(PHYMISC_REG3);
 | |
| +		core_reset = 0xffff - (1 << (lane + 8));
 | |
| +		mdio_wr(PHYMISC_REG3, val2 & core_reset);
 | |
| +	}
 | |
| +}
 | |
| +
 | |
| +void tx_restart(int lane)
 | |
| +{
 | |
| +	tx_core_assert(lane);
 | |
| +	tx_pll_assert(lane);
 | |
| +	tx_pll_de_assert(lane);
 | |
| +	usleep_range(1500, 1600);
 | |
| +	tx_core_de_assert(lane);
 | |
| +}
 | |
| +
 | |
| +void disable_lane(int lane)
 | |
| +{
 | |
| +	rx_reset_assert(lane);
 | |
| +	rx_powerdown_assert(lane);
 | |
| +	tx_core_assert(lane);
 | |
| +	lol_disable(lane);
 | |
| +}
 | |
| +
 | |
| +void toggle_reset(int lane)
 | |
| +{
 | |
| +	int reg, val, orig;
 | |
| +
 | |
| +	if (lane == ALL_LANES) {
 | |
| +		mdio_wr(PHYMISC_REG2, 0x8000);
 | |
| +		udelay(100);
 | |
| +		mdio_wr(PHYMISC_REG2, 0x0000);
 | |
| +	} else {
 | |
| +		reg = lane * 0x100 + PHYCTRL_REG8;
 | |
| +		val = (1 << 6);
 | |
| +		orig = mdio_rd(reg);
 | |
| +		mdio_wr(reg, orig + val);
 | |
| +		udelay(100);
 | |
| +		mdio_wr(reg, orig);
 | |
| +	}
 | |
| +}
 | |
| +
 | |
| +int az_complete_test(int lane)
 | |
| +{
 | |
| +	int success = 1, value;
 | |
| +
 | |
| +	if (lane == 0 || lane == ALL_LANES) {
 | |
| +		value = mdio_rd(PHYCTRL_REG5);
 | |
| +		success = success & bit_test(value, 2);
 | |
| +	}
 | |
| +	if (lane == 1 || lane == ALL_LANES) {
 | |
| +		value = mdio_rd(PHYCTRL_REG5 + 0x100);
 | |
| +		success = success & bit_test(value, 2);
 | |
| +	}
 | |
| +	if (lane == 2 || lane == ALL_LANES) {
 | |
| +		value = mdio_rd(PHYCTRL_REG5 + 0x200);
 | |
| +		success = success & bit_test(value, 2);
 | |
| +	}
 | |
| +	if (lane == 3 || lane == ALL_LANES) {
 | |
| +		value = mdio_rd(PHYCTRL_REG5 + 0x300);
 | |
| +		success = success & bit_test(value, 2);
 | |
| +	}
 | |
| +
 | |
| +	return success;
 | |
| +}
 | |
| +
 | |
| +void save_az_offsets(int lane)
 | |
| +{
 | |
| +	int i;
 | |
| +
 | |
| +#define AZ_OFFSET_LANE_UPDATE(reg, lane) \
 | |
| +	mdio_wr((reg) + (lane) * 0x100,  \
 | |
| +		(mdio_rd((reg) + (lane) * 0x100) >> 8))
 | |
| +
 | |
| +	if (lane == ALL_LANES) {
 | |
| +		for (i = 0; i < ALL_LANES; i++) {
 | |
| +			AZ_OFFSET_LANE_UPDATE(PHYMISC_REG20, i);
 | |
| +			AZ_OFFSET_LANE_UPDATE(PHYMISC_REG20 + 1, i);
 | |
| +			AZ_OFFSET_LANE_UPDATE(PHYMISC_REG20 + 2, i);
 | |
| +			AZ_OFFSET_LANE_UPDATE(PHYMISC_REG20 + 3, i);
 | |
| +			AZ_OFFSET_LANE_UPDATE(PHYMISC_REG21, i);
 | |
| +			AZ_OFFSET_LANE_UPDATE(PHYMISC_REG21 + 1, i);
 | |
| +			AZ_OFFSET_LANE_UPDATE(PHYMISC_REG21 + 2, i);
 | |
| +			AZ_OFFSET_LANE_UPDATE(PHYMISC_REG21 + 3, i);
 | |
| +			AZ_OFFSET_LANE_UPDATE(PHYMISC_REG22, i);
 | |
| +		}
 | |
| +	} else {
 | |
| +		AZ_OFFSET_LANE_UPDATE(PHYMISC_REG20, lane);
 | |
| +		AZ_OFFSET_LANE_UPDATE(PHYMISC_REG20 + 1, lane);
 | |
| +		AZ_OFFSET_LANE_UPDATE(PHYMISC_REG20 + 2, lane);
 | |
| +		AZ_OFFSET_LANE_UPDATE(PHYMISC_REG20 + 3, lane);
 | |
| +		AZ_OFFSET_LANE_UPDATE(PHYMISC_REG21, lane);
 | |
| +		AZ_OFFSET_LANE_UPDATE(PHYMISC_REG21 + 1, lane);
 | |
| +		AZ_OFFSET_LANE_UPDATE(PHYMISC_REG21 + 2, lane);
 | |
| +		AZ_OFFSET_LANE_UPDATE(PHYMISC_REG21 + 3, lane);
 | |
| +		AZ_OFFSET_LANE_UPDATE(PHYMISC_REG22, lane);
 | |
| +	}
 | |
| +
 | |
| +	mdio_wr(PHYCTRL_REG7, 0x0001);
 | |
| +}
 | |
| +
 | |
| +void save_vco_codes(int lane)
 | |
| +{
 | |
| +	int i;
 | |
| +
 | |
| +	if (lane == ALL_LANES) {
 | |
| +		for (i = 0; i < ALL_LANES; i++) {
 | |
| +			vco_codes[i] = mdio_rd(PHYMISC_REG5 + i * 0x100);
 | |
| +			mdio_wr(PHYMISC_REG5 + i * 0x100,
 | |
| +				vco_codes[i] + RX_VCO_CODE_OFFSET);
 | |
| +		}
 | |
| +	} else {
 | |
| +		vco_codes[lane] = mdio_rd(PHYMISC_REG5 + lane * 0x100);
 | |
| +		mdio_wr(PHYMISC_REG5 + lane * 0x100,
 | |
| +			vco_codes[lane] + RX_VCO_CODE_OFFSET);
 | |
| +	}
 | |
| +}
 | |
| +
 | |
| +int inphi_lane_recovery(int lane)
 | |
| +{
 | |
| +	int i, value, az_pass;
 | |
| +
 | |
| +	switch (lane) {
 | |
| +	case 0:
 | |
| +	case 1:
 | |
| +	case 2:
 | |
| +	case 3:
 | |
| +		rx_reset_assert(lane);
 | |
| +		mdelay(20);
 | |
| +		break;
 | |
| +	case ALL_LANES:
 | |
| +		mdio_wr(PHYMISC_REG2, 0x9C00);
 | |
| +		mdelay(20);
 | |
| +		do {
 | |
| +			value = mdio_rd(PHYMISC_REG2);
 | |
| +			udelay(10);
 | |
| +		} while (!bit_test(value, 4));
 | |
| +		break;
 | |
| +	default:
 | |
| +		dev_err(&inphi_phydev->mdio.dev,
 | |
| +			"Incorrect usage of APIs in %s driver\n",
 | |
| +			inphi_phydev->drv->name);
 | |
| +		break;
 | |
| +	}
 | |
| +
 | |
| +	if (lane == ALL_LANES) {
 | |
| +		for (i = 0; i < ALL_LANES; i++)
 | |
| +			mdio_wr(PHYMISC_REG7 + i * 0x100, VCO_CODE);
 | |
| +	} else {
 | |
| +		mdio_wr(PHYMISC_REG7 + lane * 0x100, VCO_CODE);
 | |
| +	}
 | |
| +
 | |
| +	if (lane == ALL_LANES)
 | |
| +		for (i = 0; i < ALL_LANES; i++)
 | |
| +			mdio_wr(PHYCTRL_REG5 + i * 0x100, 0x0418);
 | |
| +	else
 | |
| +		mdio_wr(PHYCTRL_REG5 + lane * 0x100, 0x0418);
 | |
| +
 | |
| +	mdio_wr(PHYCTRL_REG7,   0x0000);
 | |
| +
 | |
| +	rx_reset_de_assert(lane);
 | |
| +
 | |
| +	if (lane == ALL_LANES) {
 | |
| +		for (i = 0; i < ALL_LANES; i++) {
 | |
| +			mdio_wr(PHYCTRL_REG5 + i * 0x100, 0x0410);
 | |
| +			mdio_wr(PHYCTRL_REG5 + i * 0x100, 0x0412);
 | |
| +		}
 | |
| +	} else {
 | |
| +		mdio_wr(PHYCTRL_REG5 + lane * 0x100, 0x0410);
 | |
| +		mdio_wr(PHYCTRL_REG5 + lane * 0x100, 0x0412);
 | |
| +	}
 | |
| +
 | |
| +	for (i = 0; i < 64; i++) {
 | |
| +		mdelay(100);
 | |
| +		az_pass = az_complete_test(lane);
 | |
| +		if (az_pass) {
 | |
| +			save_az_offsets(lane);
 | |
| +			break;
 | |
| +		}
 | |
| +	}
 | |
| +
 | |
| +	if (!az_pass) {
 | |
| +		pr_info("in112525: AZ calibration fail @ lane=%d\n", lane);
 | |
| +		return -1;
 | |
| +	}
 | |
| +
 | |
| +	if (lane == ALL_LANES) {
 | |
| +		mdio_wr(PHYMISC_REG8, 0x0002);
 | |
| +		mdio_wr(PHYMISC_REG9, 0x2028);
 | |
| +		mdio_wr(PHYCTRL_REG6, 0x0010);
 | |
| +		usleep_range(1000, 1200);
 | |
| +		mdio_wr(PHYCTRL_REG6, 0x0110);
 | |
| +		mdelay(30);
 | |
| +		mdio_wr(PHYMISC_REG9, 0x3020);
 | |
| +	} else {
 | |
| +		mdio_wr(PHYMISC_REG4 + lane * 0x100, 0x0002);
 | |
| +		mdio_wr(PHYMISC_REG6 + lane * 0x100, 0x2028);
 | |
| +		mdio_wr(PHYCTRL_REG5 + lane * 0x100, 0x0010);
 | |
| +		usleep_range(1000, 1200);
 | |
| +		mdio_wr(PHYCTRL_REG5 + lane * 0x100, 0x0110);
 | |
| +		mdelay(30);
 | |
| +		mdio_wr(PHYMISC_REG6 + lane * 0x100, 0x3020);
 | |
| +	}
 | |
| +
 | |
| +	if (lane == ALL_LANES) {
 | |
| +		mdio_wr(PHYMISC_REG2, 0x1C00);
 | |
| +		mdio_wr(PHYMISC_REG2, 0x0C00);
 | |
| +	} else {
 | |
| +		tx_restart(lane);
 | |
| +		mdelay(11);
 | |
| +	}
 | |
| +
 | |
| +	if (lane == ALL_LANES) {
 | |
| +		if (bit_test(mdio_rd(PHYMISC_REG2), 6) == 0)
 | |
| +			return -1;
 | |
| +	} else {
 | |
| +		if (tx_pll_lock_test(lane) == 0)
 | |
| +			return -1;
 | |
| +	}
 | |
| +
 | |
| +	save_vco_codes(lane);
 | |
| +
 | |
| +	if (lane == ALL_LANES) {
 | |
| +		mdio_wr(PHYMISC_REG2, 0x0400);
 | |
| +		mdio_wr(PHYMISC_REG2, 0x0000);
 | |
| +		value = mdio_rd(PHYCTRL_REG1);
 | |
| +		value = value & 0xffbf;
 | |
| +		mdio_wr(PHYCTRL_REG2, value);
 | |
| +	} else {
 | |
| +		tx_core_de_assert(lane);
 | |
| +	}
 | |
| +
 | |
| +	if (lane == ALL_LANES) {
 | |
| +		mdio_wr(PHYMISC_REG1, 0x8000);
 | |
| +		mdio_wr(PHYMISC_REG1, 0x0000);
 | |
| +	}
 | |
| +	mdio_rd(PHYMISC_REG1);
 | |
| +	mdio_rd(PHYMISC_REG1);
 | |
| +	usleep_range(1000, 1200);
 | |
| +	mdio_rd(PHYSTAT_REG1);
 | |
| +	mdio_rd(PHYSTAT_REG2);
 | |
| +
 | |
| +	return 0;
 | |
| +}
 | |
| +
 | |
| +static void mykmod_work_handler(struct work_struct *w)
 | |
| +{
 | |
| +	int all_lanes_lock, lane0_lock, lane1_lock, lane2_lock, lane3_lock;
 | |
| +
 | |
| +	lane0_lock = bit_test(mdio_rd(0x123), 15);
 | |
| +	lane1_lock = bit_test(mdio_rd(0x223), 15);
 | |
| +	lane2_lock = bit_test(mdio_rd(0x323), 15);
 | |
| +	lane3_lock = bit_test(mdio_rd(0x423), 15);
 | |
| +
 | |
| +	/* check if the chip had any successful lane lock from the previous
 | |
| +	 * stage (e.g. u-boot)
 | |
| +	 */
 | |
| +	all_lanes_lock = lane0_lock | lane1_lock | lane2_lock | lane3_lock;
 | |
| +
 | |
| +	if (!all_lanes_lock) {
 | |
| +		/* start fresh */
 | |
| +		inphi_lane_recovery(ALL_LANES);
 | |
| +	} else {
 | |
| +		if (!lane0_lock)
 | |
| +			inphi_lane_recovery(0);
 | |
| +		if (!lane1_lock)
 | |
| +			inphi_lane_recovery(1);
 | |
| +		if (!lane2_lock)
 | |
| +			inphi_lane_recovery(2);
 | |
| +		if (!lane3_lock)
 | |
| +			inphi_lane_recovery(3);
 | |
| +	}
 | |
| +
 | |
| +	queue_delayed_work(wq, &mykmod_work, onesec);
 | |
| +}
 | |
| +
 | |
| +int inphi_probe(struct phy_device *phydev)
 | |
| +{
 | |
| +	int phy_id = 0, id_lsb = 0, id_msb = 0;
 | |
| +
 | |
| +	/* Read device id from phy registers */
 | |
| +	id_lsb = phy_read_mmd(phydev, MDIO_MMD_VEND1, INPHI_S03_DEVICE_ID_MSB);
 | |
| +	if (id_lsb < 0)
 | |
| +		return -ENXIO;
 | |
| +
 | |
| +	phy_id = id_lsb << 16;
 | |
| +
 | |
| +	id_msb = phy_read_mmd(phydev, MDIO_MMD_VEND1, INPHI_S03_DEVICE_ID_LSB);
 | |
| +	if (id_msb < 0)
 | |
| +		return -ENXIO;
 | |
| +
 | |
| +	phy_id |= id_msb;
 | |
| +
 | |
| +	/* Make sure the device tree binding matched the driver with the
 | |
| +	 * right device.
 | |
| +	 */
 | |
| +	if (phy_id != phydev->drv->phy_id) {
 | |
| +		dev_err(&phydev->mdio.dev,
 | |
| +			"Error matching phy with %s driver\n",
 | |
| +			phydev->drv->name);
 | |
| +		return -ENODEV;
 | |
| +	}
 | |
| +
 | |
| +	/* update the local phydev pointer, used inside all APIs */
 | |
| +	inphi_phydev = phydev;
 | |
| +	onesec = msecs_to_jiffies(INPHI_POLL_DELAY);
 | |
| +
 | |
| +	wq = create_singlethread_workqueue("inphi_kmod");
 | |
| +	if (wq) {
 | |
| +		queue_delayed_work(wq, &mykmod_work, onesec);
 | |
| +	} else {
 | |
| +		dev_err(&phydev->mdio.dev,
 | |
| +			"Error creating kernel workqueue for %s driver\n",
 | |
| +			phydev->drv->name);
 | |
| +		return -ENOMEM;
 | |
| +	}
 | |
| +
 | |
| +	return 0;
 | |
| +}
 | |
| +
 | |
| +static struct phy_driver inphi_driver[] = {
 | |
| +{
 | |
| +	.phy_id		= PHY_ID_IN112525,
 | |
| +	.phy_id_mask	= 0x0ff0fff0,
 | |
| +	.name		= "Inphi 112525_S03",
 | |
| +	.features	= PHY_GBIT_FEATURES,
 | |
| +	.probe		= &inphi_probe,
 | |
| +},
 | |
| +};
 | |
| +
 | |
| +module_phy_driver(inphi_driver);
 | |
| +
 | |
| +static struct mdio_device_id __maybe_unused inphi_tbl[] = {
 | |
| +	{ PHY_ID_IN112525, 0x0ff0fff0},
 | |
| +	{},
 | |
| +};
 | |
| +
 | |
| +MODULE_DEVICE_TABLE(mdio, inphi_tbl);
 |