162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0+
262306a36Sopenharmony_ci/*
362306a36Sopenharmony_ci * drivers/net/phy/cicada.c
462306a36Sopenharmony_ci *
562306a36Sopenharmony_ci * Driver for Cicada PHYs
662306a36Sopenharmony_ci *
762306a36Sopenharmony_ci * Author: Andy Fleming
862306a36Sopenharmony_ci *
962306a36Sopenharmony_ci * Copyright (c) 2004 Freescale Semiconductor, Inc.
1062306a36Sopenharmony_ci */
1162306a36Sopenharmony_ci#include <linux/kernel.h>
1262306a36Sopenharmony_ci#include <linux/string.h>
1362306a36Sopenharmony_ci#include <linux/errno.h>
1462306a36Sopenharmony_ci#include <linux/unistd.h>
1562306a36Sopenharmony_ci#include <linux/interrupt.h>
1662306a36Sopenharmony_ci#include <linux/init.h>
1762306a36Sopenharmony_ci#include <linux/delay.h>
1862306a36Sopenharmony_ci#include <linux/netdevice.h>
1962306a36Sopenharmony_ci#include <linux/etherdevice.h>
2062306a36Sopenharmony_ci#include <linux/skbuff.h>
2162306a36Sopenharmony_ci#include <linux/spinlock.h>
2262306a36Sopenharmony_ci#include <linux/mm.h>
2362306a36Sopenharmony_ci#include <linux/module.h>
2462306a36Sopenharmony_ci#include <linux/mii.h>
2562306a36Sopenharmony_ci#include <linux/ethtool.h>
2662306a36Sopenharmony_ci#include <linux/phy.h>
2762306a36Sopenharmony_ci
2862306a36Sopenharmony_ci#include <linux/io.h>
2962306a36Sopenharmony_ci#include <asm/irq.h>
3062306a36Sopenharmony_ci#include <linux/uaccess.h>
3162306a36Sopenharmony_ci
3262306a36Sopenharmony_ci/* Cicada Extended Control Register 1 */
3362306a36Sopenharmony_ci#define MII_CIS8201_EXT_CON1           0x17
3462306a36Sopenharmony_ci#define MII_CIS8201_EXTCON1_INIT       0x0000
3562306a36Sopenharmony_ci
3662306a36Sopenharmony_ci/* Cicada Interrupt Mask Register */
3762306a36Sopenharmony_ci#define MII_CIS8201_IMASK		0x19
3862306a36Sopenharmony_ci#define MII_CIS8201_IMASK_IEN		0x8000
3962306a36Sopenharmony_ci#define MII_CIS8201_IMASK_SPEED	0x4000
4062306a36Sopenharmony_ci#define MII_CIS8201_IMASK_LINK		0x2000
4162306a36Sopenharmony_ci#define MII_CIS8201_IMASK_DUPLEX	0x1000
4262306a36Sopenharmony_ci#define MII_CIS8201_IMASK_MASK		0xf000
4362306a36Sopenharmony_ci
4462306a36Sopenharmony_ci/* Cicada Interrupt Status Register */
4562306a36Sopenharmony_ci#define MII_CIS8201_ISTAT		0x1a
4662306a36Sopenharmony_ci#define MII_CIS8201_ISTAT_STATUS	0x8000
4762306a36Sopenharmony_ci#define MII_CIS8201_ISTAT_SPEED	0x4000
4862306a36Sopenharmony_ci#define MII_CIS8201_ISTAT_LINK		0x2000
4962306a36Sopenharmony_ci#define MII_CIS8201_ISTAT_DUPLEX	0x1000
5062306a36Sopenharmony_ci
5162306a36Sopenharmony_ci/* Cicada Auxiliary Control/Status Register */
5262306a36Sopenharmony_ci#define MII_CIS8201_AUX_CONSTAT        0x1c
5362306a36Sopenharmony_ci#define MII_CIS8201_AUXCONSTAT_INIT    0x0004
5462306a36Sopenharmony_ci#define MII_CIS8201_AUXCONSTAT_DUPLEX  0x0020
5562306a36Sopenharmony_ci#define MII_CIS8201_AUXCONSTAT_SPEED   0x0018
5662306a36Sopenharmony_ci#define MII_CIS8201_AUXCONSTAT_GBIT    0x0010
5762306a36Sopenharmony_ci#define MII_CIS8201_AUXCONSTAT_100     0x0008
5862306a36Sopenharmony_ci
5962306a36Sopenharmony_ciMODULE_DESCRIPTION("Cicadia PHY driver");
6062306a36Sopenharmony_ciMODULE_AUTHOR("Andy Fleming");
6162306a36Sopenharmony_ciMODULE_LICENSE("GPL");
6262306a36Sopenharmony_ci
6362306a36Sopenharmony_cistatic int cis820x_config_init(struct phy_device *phydev)
6462306a36Sopenharmony_ci{
6562306a36Sopenharmony_ci	int err;
6662306a36Sopenharmony_ci
6762306a36Sopenharmony_ci	err = phy_write(phydev, MII_CIS8201_AUX_CONSTAT,
6862306a36Sopenharmony_ci			MII_CIS8201_AUXCONSTAT_INIT);
6962306a36Sopenharmony_ci
7062306a36Sopenharmony_ci	if (err < 0)
7162306a36Sopenharmony_ci		return err;
7262306a36Sopenharmony_ci
7362306a36Sopenharmony_ci	err = phy_write(phydev, MII_CIS8201_EXT_CON1,
7462306a36Sopenharmony_ci			MII_CIS8201_EXTCON1_INIT);
7562306a36Sopenharmony_ci
7662306a36Sopenharmony_ci	return err;
7762306a36Sopenharmony_ci}
7862306a36Sopenharmony_ci
7962306a36Sopenharmony_cistatic int cis820x_ack_interrupt(struct phy_device *phydev)
8062306a36Sopenharmony_ci{
8162306a36Sopenharmony_ci	int err = phy_read(phydev, MII_CIS8201_ISTAT);
8262306a36Sopenharmony_ci
8362306a36Sopenharmony_ci	return (err < 0) ? err : 0;
8462306a36Sopenharmony_ci}
8562306a36Sopenharmony_ci
8662306a36Sopenharmony_cistatic int cis820x_config_intr(struct phy_device *phydev)
8762306a36Sopenharmony_ci{
8862306a36Sopenharmony_ci	int err;
8962306a36Sopenharmony_ci
9062306a36Sopenharmony_ci	if (phydev->interrupts == PHY_INTERRUPT_ENABLED) {
9162306a36Sopenharmony_ci		err = cis820x_ack_interrupt(phydev);
9262306a36Sopenharmony_ci		if (err)
9362306a36Sopenharmony_ci			return err;
9462306a36Sopenharmony_ci
9562306a36Sopenharmony_ci		err = phy_write(phydev, MII_CIS8201_IMASK,
9662306a36Sopenharmony_ci				MII_CIS8201_IMASK_MASK);
9762306a36Sopenharmony_ci	} else {
9862306a36Sopenharmony_ci		err = phy_write(phydev, MII_CIS8201_IMASK, 0);
9962306a36Sopenharmony_ci		if (err)
10062306a36Sopenharmony_ci			return err;
10162306a36Sopenharmony_ci
10262306a36Sopenharmony_ci		err = cis820x_ack_interrupt(phydev);
10362306a36Sopenharmony_ci	}
10462306a36Sopenharmony_ci
10562306a36Sopenharmony_ci	return err;
10662306a36Sopenharmony_ci}
10762306a36Sopenharmony_ci
10862306a36Sopenharmony_cistatic irqreturn_t cis820x_handle_interrupt(struct phy_device *phydev)
10962306a36Sopenharmony_ci{
11062306a36Sopenharmony_ci	int irq_status;
11162306a36Sopenharmony_ci
11262306a36Sopenharmony_ci	irq_status = phy_read(phydev, MII_CIS8201_ISTAT);
11362306a36Sopenharmony_ci	if (irq_status < 0) {
11462306a36Sopenharmony_ci		phy_error(phydev);
11562306a36Sopenharmony_ci		return IRQ_NONE;
11662306a36Sopenharmony_ci	}
11762306a36Sopenharmony_ci
11862306a36Sopenharmony_ci	if (!(irq_status & MII_CIS8201_IMASK_MASK))
11962306a36Sopenharmony_ci		return IRQ_NONE;
12062306a36Sopenharmony_ci
12162306a36Sopenharmony_ci	phy_trigger_machine(phydev);
12262306a36Sopenharmony_ci
12362306a36Sopenharmony_ci	return IRQ_HANDLED;
12462306a36Sopenharmony_ci}
12562306a36Sopenharmony_ci
12662306a36Sopenharmony_ci/* Cicada 8201, a.k.a Vitesse VSC8201 */
12762306a36Sopenharmony_cistatic struct phy_driver cis820x_driver[] = {
12862306a36Sopenharmony_ci{
12962306a36Sopenharmony_ci	.phy_id		= 0x000fc410,
13062306a36Sopenharmony_ci	.name		= "Cicada Cis8201",
13162306a36Sopenharmony_ci	.phy_id_mask	= 0x000ffff0,
13262306a36Sopenharmony_ci	/* PHY_GBIT_FEATURES */
13362306a36Sopenharmony_ci	.config_init	= &cis820x_config_init,
13462306a36Sopenharmony_ci	.config_intr	= &cis820x_config_intr,
13562306a36Sopenharmony_ci	.handle_interrupt = &cis820x_handle_interrupt,
13662306a36Sopenharmony_ci}, {
13762306a36Sopenharmony_ci	.phy_id		= 0x000fc440,
13862306a36Sopenharmony_ci	.name		= "Cicada Cis8204",
13962306a36Sopenharmony_ci	.phy_id_mask	= 0x000fffc0,
14062306a36Sopenharmony_ci	/* PHY_GBIT_FEATURES */
14162306a36Sopenharmony_ci	.config_init	= &cis820x_config_init,
14262306a36Sopenharmony_ci	.config_intr	= &cis820x_config_intr,
14362306a36Sopenharmony_ci	.handle_interrupt = &cis820x_handle_interrupt,
14462306a36Sopenharmony_ci} };
14562306a36Sopenharmony_ci
14662306a36Sopenharmony_cimodule_phy_driver(cis820x_driver);
14762306a36Sopenharmony_ci
14862306a36Sopenharmony_cistatic struct mdio_device_id __maybe_unused cicada_tbl[] = {
14962306a36Sopenharmony_ci	{ 0x000fc410, 0x000ffff0 },
15062306a36Sopenharmony_ci	{ 0x000fc440, 0x000fffc0 },
15162306a36Sopenharmony_ci	{ }
15262306a36Sopenharmony_ci};
15362306a36Sopenharmony_ci
15462306a36Sopenharmony_ciMODULE_DEVICE_TABLE(mdio, cicada_tbl);
155