cachepc-linux

Fork of AMDESE/linux with modifications for CachePC side-channel attack
git clone https://git.sinitax.com/sinitax/cachepc-linux
Log | Files | Refs | README | LICENSE | sfeed.txt

meson-gxl.c (7739B)


      1// SPDX-License-Identifier: GPL-2.0+
      2/*
      3 * Amlogic Meson GXL Internal PHY Driver
      4 *
      5 * Copyright (C) 2015 Amlogic, Inc. All rights reserved.
      6 * Copyright (C) 2016 BayLibre, SAS. All rights reserved.
      7 * Author: Neil Armstrong <narmstrong@baylibre.com>
      8 */
      9#include <linux/kernel.h>
     10#include <linux/module.h>
     11#include <linux/mii.h>
     12#include <linux/ethtool.h>
     13#include <linux/phy.h>
     14#include <linux/netdevice.h>
     15#include <linux/bitfield.h>
     16
     17#define TSTCNTL		20
     18#define  TSTCNTL_READ		BIT(15)
     19#define  TSTCNTL_WRITE		BIT(14)
     20#define  TSTCNTL_REG_BANK_SEL	GENMASK(12, 11)
     21#define  TSTCNTL_TEST_MODE	BIT(10)
     22#define  TSTCNTL_READ_ADDRESS	GENMASK(9, 5)
     23#define  TSTCNTL_WRITE_ADDRESS	GENMASK(4, 0)
     24#define TSTREAD1	21
     25#define TSTWRITE	23
     26#define INTSRC_FLAG	29
     27#define  INTSRC_ANEG_PR		BIT(1)
     28#define  INTSRC_PARALLEL_FAULT	BIT(2)
     29#define  INTSRC_ANEG_LP_ACK	BIT(3)
     30#define  INTSRC_LINK_DOWN	BIT(4)
     31#define  INTSRC_REMOTE_FAULT	BIT(5)
     32#define  INTSRC_ANEG_COMPLETE	BIT(6)
     33#define  INTSRC_ENERGY_DETECT	BIT(7)
     34#define INTSRC_MASK	30
     35
     36#define INT_SOURCES (INTSRC_LINK_DOWN | INTSRC_ANEG_COMPLETE | \
     37		     INTSRC_ENERGY_DETECT)
     38
     39#define BANK_ANALOG_DSP		0
     40#define BANK_WOL		1
     41#define BANK_BIST		3
     42
     43/* WOL Registers */
     44#define LPI_STATUS	0xc
     45#define  LPI_STATUS_RSV12	BIT(12)
     46
     47/* BIST Registers */
     48#define FR_PLL_CONTROL	0x1b
     49#define FR_PLL_DIV0	0x1c
     50#define FR_PLL_DIV1	0x1d
     51
     52static int meson_gxl_open_banks(struct phy_device *phydev)
     53{
     54	int ret;
     55
     56	/* Enable Analog and DSP register Bank access by
     57	 * toggling TSTCNTL_TEST_MODE bit in the TSTCNTL register
     58	 */
     59	ret = phy_write(phydev, TSTCNTL, 0);
     60	if (ret)
     61		return ret;
     62	ret = phy_write(phydev, TSTCNTL, TSTCNTL_TEST_MODE);
     63	if (ret)
     64		return ret;
     65	ret = phy_write(phydev, TSTCNTL, 0);
     66	if (ret)
     67		return ret;
     68	return phy_write(phydev, TSTCNTL, TSTCNTL_TEST_MODE);
     69}
     70
     71static void meson_gxl_close_banks(struct phy_device *phydev)
     72{
     73	phy_write(phydev, TSTCNTL, 0);
     74}
     75
     76static int meson_gxl_read_reg(struct phy_device *phydev,
     77			      unsigned int bank, unsigned int reg)
     78{
     79	int ret;
     80
     81	ret = meson_gxl_open_banks(phydev);
     82	if (ret)
     83		goto out;
     84
     85	ret = phy_write(phydev, TSTCNTL, TSTCNTL_READ |
     86			FIELD_PREP(TSTCNTL_REG_BANK_SEL, bank) |
     87			TSTCNTL_TEST_MODE |
     88			FIELD_PREP(TSTCNTL_READ_ADDRESS, reg));
     89	if (ret)
     90		goto out;
     91
     92	ret = phy_read(phydev, TSTREAD1);
     93out:
     94	/* Close the bank access on our way out */
     95	meson_gxl_close_banks(phydev);
     96	return ret;
     97}
     98
     99static int meson_gxl_write_reg(struct phy_device *phydev,
    100			       unsigned int bank, unsigned int reg,
    101			       uint16_t value)
    102{
    103	int ret;
    104
    105	ret = meson_gxl_open_banks(phydev);
    106	if (ret)
    107		goto out;
    108
    109	ret = phy_write(phydev, TSTWRITE, value);
    110	if (ret)
    111		goto out;
    112
    113	ret = phy_write(phydev, TSTCNTL, TSTCNTL_WRITE |
    114			FIELD_PREP(TSTCNTL_REG_BANK_SEL, bank) |
    115			TSTCNTL_TEST_MODE |
    116			FIELD_PREP(TSTCNTL_WRITE_ADDRESS, reg));
    117
    118out:
    119	/* Close the bank access on our way out */
    120	meson_gxl_close_banks(phydev);
    121	return ret;
    122}
    123
    124static int meson_gxl_config_init(struct phy_device *phydev)
    125{
    126	int ret;
    127
    128	/* Enable fractional PLL */
    129	ret = meson_gxl_write_reg(phydev, BANK_BIST, FR_PLL_CONTROL, 0x5);
    130	if (ret)
    131		return ret;
    132
    133	/* Program fraction FR_PLL_DIV1 */
    134	ret = meson_gxl_write_reg(phydev, BANK_BIST, FR_PLL_DIV1, 0x029a);
    135	if (ret)
    136		return ret;
    137
    138	/* Program fraction FR_PLL_DIV1 */
    139	ret = meson_gxl_write_reg(phydev, BANK_BIST, FR_PLL_DIV0, 0xaaaa);
    140	if (ret)
    141		return ret;
    142
    143	return 0;
    144}
    145
    146/* This function is provided to cope with the possible failures of this phy
    147 * during aneg process. When aneg fails, the PHY reports that aneg is done
    148 * but the value found in MII_LPA is wrong:
    149 *  - Early failures: MII_LPA is just 0x0001. if MII_EXPANSION reports that
    150 *    the link partner (LP) supports aneg but the LP never acked our base
    151 *    code word, it is likely that we never sent it to begin with.
    152 *  - Late failures: MII_LPA is filled with a value which seems to make sense
    153 *    but it actually is not what the LP is advertising. It seems that we
    154 *    can detect this using a magic bit in the WOL bank (reg 12 - bit 12).
    155 *    If this particular bit is not set when aneg is reported being done,
    156 *    it means MII_LPA is likely to be wrong.
    157 *
    158 * In both case, forcing a restart of the aneg process solve the problem.
    159 * When this failure happens, the first retry is usually successful but,
    160 * in some cases, it may take up to 6 retries to get a decent result
    161 */
    162static int meson_gxl_read_status(struct phy_device *phydev)
    163{
    164	int ret, wol, lpa, exp;
    165
    166	if (phydev->autoneg == AUTONEG_ENABLE) {
    167		ret = genphy_aneg_done(phydev);
    168		if (ret < 0)
    169			return ret;
    170		else if (!ret)
    171			goto read_status_continue;
    172
    173		/* Aneg is done, let's check everything is fine */
    174		wol = meson_gxl_read_reg(phydev, BANK_WOL, LPI_STATUS);
    175		if (wol < 0)
    176			return wol;
    177
    178		lpa = phy_read(phydev, MII_LPA);
    179		if (lpa < 0)
    180			return lpa;
    181
    182		exp = phy_read(phydev, MII_EXPANSION);
    183		if (exp < 0)
    184			return exp;
    185
    186		if (!(wol & LPI_STATUS_RSV12) ||
    187		    ((exp & EXPANSION_NWAY) && !(lpa & LPA_LPACK))) {
    188			/* Looks like aneg failed after all */
    189			phydev_dbg(phydev, "LPA corruption - aneg restart\n");
    190			return genphy_restart_aneg(phydev);
    191		}
    192	}
    193
    194read_status_continue:
    195	return genphy_read_status(phydev);
    196}
    197
    198static int meson_gxl_ack_interrupt(struct phy_device *phydev)
    199{
    200	int ret = phy_read(phydev, INTSRC_FLAG);
    201
    202	return ret < 0 ? ret : 0;
    203}
    204
    205static int meson_gxl_config_intr(struct phy_device *phydev)
    206{
    207	int ret;
    208
    209	if (phydev->interrupts == PHY_INTERRUPT_ENABLED) {
    210		/* Ack any pending IRQ */
    211		ret = meson_gxl_ack_interrupt(phydev);
    212		if (ret)
    213			return ret;
    214
    215		ret = phy_write(phydev, INTSRC_MASK, INT_SOURCES);
    216	} else {
    217		ret = phy_write(phydev, INTSRC_MASK, 0);
    218
    219		/* Ack any pending IRQ */
    220		ret = meson_gxl_ack_interrupt(phydev);
    221	}
    222
    223	return ret;
    224}
    225
    226static irqreturn_t meson_gxl_handle_interrupt(struct phy_device *phydev)
    227{
    228	int irq_status;
    229
    230	irq_status = phy_read(phydev, INTSRC_FLAG);
    231	if (irq_status < 0) {
    232		phy_error(phydev);
    233		return IRQ_NONE;
    234	}
    235
    236	irq_status &= INT_SOURCES;
    237
    238	if (irq_status == 0)
    239		return IRQ_NONE;
    240
    241	/* Aneg-complete interrupt is used for link-up detection */
    242	if (phydev->autoneg == AUTONEG_ENABLE &&
    243	    irq_status == INTSRC_ENERGY_DETECT)
    244		return IRQ_HANDLED;
    245
    246	/* Give PHY some time before MAC starts sending data. This works
    247	 * around an issue where network doesn't come up properly.
    248	 */
    249	if (!(irq_status & INTSRC_LINK_DOWN))
    250		phy_queue_state_machine(phydev, msecs_to_jiffies(100));
    251	else
    252		phy_trigger_machine(phydev);
    253
    254	return IRQ_HANDLED;
    255}
    256
    257static struct phy_driver meson_gxl_phy[] = {
    258	{
    259		PHY_ID_MATCH_EXACT(0x01814400),
    260		.name		= "Meson GXL Internal PHY",
    261		/* PHY_BASIC_FEATURES */
    262		.flags		= PHY_IS_INTERNAL,
    263		.soft_reset     = genphy_soft_reset,
    264		.config_init	= meson_gxl_config_init,
    265		.read_status	= meson_gxl_read_status,
    266		.config_intr	= meson_gxl_config_intr,
    267		.handle_interrupt = meson_gxl_handle_interrupt,
    268		.suspend        = genphy_suspend,
    269		.resume         = genphy_resume,
    270	}, {
    271		PHY_ID_MATCH_EXACT(0x01803301),
    272		.name		= "Meson G12A Internal PHY",
    273		/* PHY_BASIC_FEATURES */
    274		.flags		= PHY_IS_INTERNAL,
    275		.soft_reset     = genphy_soft_reset,
    276		.config_intr	= meson_gxl_config_intr,
    277		.handle_interrupt = meson_gxl_handle_interrupt,
    278		.suspend        = genphy_suspend,
    279		.resume         = genphy_resume,
    280	},
    281};
    282
    283static struct mdio_device_id __maybe_unused meson_gxl_tbl[] = {
    284	{ PHY_ID_MATCH_VENDOR(0x01814400) },
    285	{ PHY_ID_MATCH_VENDOR(0x01803301) },
    286	{ }
    287};
    288
    289module_phy_driver(meson_gxl_phy);
    290
    291MODULE_DEVICE_TABLE(mdio, meson_gxl_tbl);
    292
    293MODULE_DESCRIPTION("Amlogic Meson GXL Internal PHY driver");
    294MODULE_AUTHOR("Baoqi wang");
    295MODULE_AUTHOR("Neil Armstrong <narmstrong@baylibre.com>");
    296MODULE_AUTHOR("Jerome Brunet <jbrunet@baylibre.com>");
    297MODULE_LICENSE("GPL");