summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorSerge Semin <fancer.lancer@gmail.com>2023-07-26 16:54:13 +0300
committerSerge Semin <fancer.lancer@gmail.com>2026-08-13 20:38:19 +0300
commit1235ead764fbc95ae89c5c2f95783c654e3d2603 (patch)
tree811da1e7b286892f5fee26502cfde944724fc00d
parent01260cda90e449d341173748237ee956ea35ca37 (diff)
downloadlinux-1235ead764fbc95ae89c5c2f95783c654e3d2603.tar.gz
linux-1235ead764fbc95ae89c5c2f95783c654e3d2603.zip
net: phy: marvell-88x2222: Add GPIOs/SFP+ support
Marvell 88x2222 PHY is equipped with 11 general purpose pins, which can be configured to work either as a special-function pins (LEDs, TWSI SDA/SCL, SFP+, etc) or as GPIOs. In the later case they can be registered in the kernel GPIO subsystem and used then either for some platform-specific sake or as the SFP+ port GPIOs seeing there is no MMD-based SFP+ implementation currently supported by the kernel. All eleven GPIOs can support input and output (open-drain) modes determined by the TRISTATE control register. In case of the input mode the state change is reported by means of the PHY IRQ line. Each GPIO interrupt can be individually enabled/disabled by the IRQ Enable register. There are all five interrupt types supported: high/low level triggered, rising/falling/both edges triggered. Note GPIO IRQs are handled in a separate ISR instead of doing that in the phy_driver.handle_interrupt() function. The later handler is utilized only if the PHY is attached to a network device. It's unacceptable for the general-purpose IO pins since they can be utilized anytime the device is successfully probed. For a similar reason the GPIOs aren't supported if the device-specific hard-reset line is specified. It's toggled during the PHY re-attachment which will clear the entire device state out and will break the GPIOs driver functionality for sure. Note 1. In case of some of the GPIOs are marked as invalid the respective pins special-function is activated/preserved. The pins can be defined as reserved for instance by means of the "gpio-reserved-ranges" property. It will be useful for the platforms which rely on the special-functions, like LEDs, activated. Note 2. The private data caches are utilized in order to implement the access to some of the GPIO CSRs. It's done for several reasons: in order to implement the IRQ-chip support for a device living on a slow asynchronous bus; in order speed up the CSRs access; in order to overcome a race condition around the GPIO Data register RMW-access (see the commit body for details). Signed-off-by: Serge Semin <fancer.lancer@gmail.com>
-rw-r--r--drivers/net/phy/Kconfig7
-rw-r--r--drivers/net/phy/marvell-88x2222.c658
2 files changed, 665 insertions, 0 deletions
diff --git a/drivers/net/phy/Kconfig b/drivers/net/phy/Kconfig
index 7b73332a13d95..d1f578955b7f3 100644
--- a/drivers/net/phy/Kconfig
+++ b/drivers/net/phy/Kconfig
@@ -277,6 +277,13 @@ config MARVELL_88X2222_PHY
Support for the Marvell 88X2222 Dual-port Multi-speed Ethernet
Transceiver.
+config MARVELL_88X2222_GPIO
+ bool "Marvell 88X2222 SFP+ GPIOs support"
+ depends on MARVELL_88X2222_PHY
+ depends on GPIOLIB && GPIOLIB_IRQCHIP
+ help
+ Support for the Marvell 88X2222 PHY SFP+/GPIO signals.
+
config MAXLINEAR_GPHY
tristate "Maxlinear Ethernet PHYs"
select POLYNOMIAL if HWMON
diff --git a/drivers/net/phy/marvell-88x2222.c b/drivers/net/phy/marvell-88x2222.c
index 2f6950ce12125..46807891a253c 100644
--- a/drivers/net/phy/marvell-88x2222.c
+++ b/drivers/net/phy/marvell-88x2222.c
@@ -10,8 +10,11 @@
#include <linux/bitfield.h>
#include <linux/bits.h>
#include <linux/irq.h>
+#include <linux/kernel.h>
#include <linux/module.h>
+#include <linux/mutex.h>
#include <linux/phy.h>
+#include <linux/gpio/driver.h>
#include <linux/delay.h>
#include <linux/mdio.h>
#include <linux/marvell_phy.h>
@@ -19,6 +22,8 @@
#include <linux/phy_port.h>
#include <linux/netdevice.h>
+#include <dt-bindings/net/mv-phy-88x2222.h>
+
/* Port PCS Configuration */
#define MV_PCS_CONFIG 0xF002
#define MV_PCS_HOST_PCS_SELECT GENMASK(6, 0)
@@ -35,6 +40,68 @@
#define MV_HOST_RST_SW BIT(7)
#define MV_PORT_RST_SW (MV_LINE_RST_SW | MV_HOST_RST_SW)
+/* GPIO Interrupt Enable */
+#define MV_GPIO_INT_EN 0xF010
+
+/* GPIO Interrupt Status */
+#define MV_GPIO_INT_STAT 0xF011
+
+/* GPIO Input/Output data */
+#define MV_GPIO_DATA 0xF012
+
+/* GPIO Tristate control */
+#define MV_GPIO_TRISTATE_CTRL 0xF013
+
+/* GPIO Interrupt type 1 (GPIOs 0 - 3) */
+#define MV_GPIO_INT_TYPE1 0xF014
+#define MV_GPIO_INT_TYPE_PIN0 GENMASK(2, 0)
+#define MV_GPIO_INT_NO_IRQ 0
+#define MV_GPIO_INT_LEVEL_LOW 2
+#define MV_GPIO_INT_LEVEL_HIGH 3
+#define MV_GPIO_INT_EDGE_FALLING 4
+#define MV_GPIO_INT_EDGE_RISING 5
+#define MV_GPIO_INT_EDGE_BOTH 7
+#define MV_GPIO_INT_TYPE_PIN1 GENMASK(6, 4)
+#define MV_GPIO_FUNC_TX_FLT_PIN1 BIT(7)
+#define MV_GPIO_INT_TYPE_PIN2 GENMASK(10, 8)
+#define MV_GPIO_FUNC_RX_LOS_PIN2 BIT(11)
+#define MV_GPIO_INT_TYPE_PIN3 GENMASK(14, 12)
+
+/* GPIO Interrupt type 2 (GPIOs 4 - 7) */
+#define MV_GPIO_INT_TYPE2 0xF015
+#define MV_GPIO_INT_TYPE_PIN4 GENMASK(2, 0)
+#define MV_GPIO_FUNC_LED0_PIN4 BIT(3)
+#define MV_GPIO_INT_TYPE_PIN5 GENMASK(6, 4)
+#define MV_GPIO_FUNC_LED1_PIN5 BIT(7)
+#define MV_GPIO_INT_TYPE_PIN6 GENMASK(10, 8)
+#define MV_GPIO_FUNC_MPC_PIN6 BIT(11)
+#define MV_GPIO_INT_TYPE_PIN7 GENMASK(14, 12)
+#define MV_GPIO_FUNC_TOD_PIN7 BIT(15)
+
+/* GPIO Interrupt type 3 (GPIOs 8, 10, 11) */
+#define MV_GPIO_INT_TYPE3 0xF016
+#define MV_GPIO_INT_TYPE_PIN8 GENMASK(2, 0)
+#define MV_GPIO_FUNC_TX_DIS_PIN8 GENMASK(4, 3)
+#define MV_GPIO_FUNC_TX_DIS_LED 0
+#define MV_GPIO_FUNC_TX_DIS_PIN 1
+#define MV_GPIO_FUNC_TX_DIS_REG 2
+#define MV_GPIO_INT_TYPE_PIN10 GENMASK(10, 8)
+#define MV_GPIO_FUNC_SDA_PIN10 BIT(11)
+#define MV_GPIO_INT_TYPE_PIN11 GENMASK(14, 12)
+#define MV_GPIO_FUNC_SCL_PIN11 BIT(15)
+
+/* GPIO Interrupt type 1-3 helpers */
+#define MV_GPIO_INT_TYPE_REG(_pin) \
+ ((_pin) / 4)
+#define MV_GPIO_INT_TYPE_PREP(_pin, _val) \
+ ((_val) << ((_pin) % 4) * 4)
+#define MV_GPIO_INT_TYPE_MASK(_pin) \
+ MV_GPIO_INT_TYPE_PREP(_pin, GENMASK(2, 0))
+#define MV_GPIO_FUNC_PREP(_pin, _val) \
+ ((_val) << (3 + ((_pin) % 4) * 4))
+#define MV_GPIO_FUNC_MASK(_pin) \
+ MV_GPIO_FUNC_PREP(_pin, (_pin) != 8 ? BIT(0) : GENMASK(1, 0))
+
/* Port Interrupt Status */
#define MV_PORT_INT_STAT 0xF040
#define MV_PORT_INT_PCS_LINE BIT(0)
@@ -144,6 +211,11 @@
/* Host-side XAUI Interrupt Status 2 Register */
#define MV_HOST_XAUI_INT_STAT2 0x9004
+/* GPIO controller settings (#9 always invalid, #0 and #3 always available) */
+#define MV_GPIO_NUM 12
+#define MV_GPIO_VAL_MASK 0xDFF
+#define MV_GPIO_INT_UPD_FLAG BIT(31)
+
#define AUTONEG_TIMEOUT 3
struct mv2222_data {
@@ -152,6 +224,20 @@ struct mv2222_data {
bool sfp_link;
};
+#ifdef CONFIG_MARVELL_88X2222_GPIO
+struct mv2222_gpio {
+ struct gpio_chip gc;
+ struct phy_device *phydev;
+ unsigned int irq;
+ u32 cache_val_mask;
+ u32 cache_data;
+ u32 cache_tri_ctrl;
+ u32 cache_int_en;
+ u32 cache_int_type[MV_GPIO_INT_TYPE_REG(MV_GPIO_NUM)];
+ struct mutex cache_lock;
+};
+#endif
+
/* SFI PMA transmit enable */
static int mv2222_tx_enable(struct phy_device *phydev)
{
@@ -930,6 +1016,573 @@ static int mv2222_config_init(struct phy_device *phydev)
return mv2222_host_reset(phydev);
}
+#ifdef CONFIG_MARVELL_88X2222_GPIO
+/*
+ * Marvell 88x2222 GPIO control registers access is implemented by means of the
+ * private data caches. It has several reasons. First of all the IRQ chip
+ * implementation requires the IRQ-related settings applied in a non-atomic
+ * context for the slow asynchronously accessed bus. Thus caching the IRQ-masks
+ * and types is neccessary in order to flush the respective CSRs data in the
+ * may-sleep context. Second the implementation decreases a bus traffic which
+ * improves the accesses performance espcially in case of the bit-banged MDIO
+ * bus. Finally it prevents a race condition in the output value setup on the
+ * output GPIO-mode settings. The GPIO data register has a multiplexed input
+ * output data semantics. Any read from the CSRs returns either the input GPIO
+ * state or the output GPIO value. Meanwhile any write to the data register
+ * updates the output GPIO value. So the race condition may happen if the
+ * output value is updated before the output GPIO-mode is activated and if
+ * there is a parallel read-modify-write is performed to the GPIO data
+ * register. In that case the input GPIO state will be read and written back as
+ * the output GPIO value. Using the private data cache prevents that.
+ */
+
+static inline bool mv2222_gpio_offset_is_valid(unsigned int ofs)
+{
+ return ofs < MV_GPIO_NUM && ofs != 9;
+}
+
+static int mv2222_gpio_read_tri_ctrl(struct mv2222_gpio *gpio)
+{
+ int val;
+
+ mutex_lock(&gpio->cache_lock);
+
+ val = phy_read_mmd(gpio->phydev, MDIO_MMD_VEND2, MV_GPIO_TRISTATE_CTRL);
+ if (val < 0)
+ goto err_mutex_unlock;
+
+ gpio->cache_tri_ctrl = val;
+
+err_mutex_unlock:
+ mutex_unlock(&gpio->cache_lock);
+
+ return val;
+}
+
+static int mv2222_gpio_wthru_tri_ctrl(struct mv2222_gpio *gpio, u16 mask, u16 val)
+{
+ int ret;
+
+ mutex_lock(&gpio->cache_lock);
+
+ gpio->cache_tri_ctrl &= ~mask;
+ gpio->cache_tri_ctrl |= val;
+
+ ret = phy_write_mmd(gpio->phydev, MDIO_MMD_VEND2,
+ MV_GPIO_TRISTATE_CTRL, gpio->cache_tri_ctrl);
+
+ mutex_unlock(&gpio->cache_lock);
+
+ return ret;
+}
+
+static int mv2222_gpio_read_data(struct mv2222_gpio *gpio)
+{
+ int val;
+
+ mutex_lock(&gpio->cache_lock);
+
+ val = phy_read_mmd(gpio->phydev, MDIO_MMD_VEND2, MV_GPIO_DATA);
+ if (val < 0)
+ goto err_mutex_unlock;
+
+ gpio->cache_data = val;
+
+err_mutex_unlock:
+ mutex_unlock(&gpio->cache_lock);
+
+ return val;
+}
+
+static int mv2222_gpio_wthru_data(struct mv2222_gpio *gpio, u16 mask, u16 val)
+{
+ int ret;
+
+ mutex_lock(&gpio->cache_lock);
+
+ gpio->cache_data &= ~mask;
+ gpio->cache_data |= val;
+
+ ret = phy_write_mmd(gpio->phydev, MDIO_MMD_VEND2,
+ MV_GPIO_DATA, gpio->cache_data);
+
+ mutex_unlock(&gpio->cache_lock);
+
+ return ret;
+}
+
+static int mv2222_gpio_cache_int_en(struct mv2222_gpio *gpio,
+ unsigned int ofs, bool int_en)
+{
+ if (!mv2222_gpio_offset_is_valid(ofs))
+ return -EINVAL;
+
+ if (int_en)
+ gpio->cache_int_en |= BIT(ofs);
+ else
+ gpio->cache_int_en &= ~BIT(ofs);
+
+ gpio->cache_int_en |= MV_GPIO_INT_UPD_FLAG;
+
+ return 0;
+}
+
+static int mv2222_gpio_cache_func(struct mv2222_gpio *gpio,
+ unsigned int ofs, bool gpio_en)
+{
+ u16 val;
+ int reg;
+
+ if (!mv2222_gpio_offset_is_valid(ofs))
+ return -EINVAL;
+
+ /* Pins #0 and #3 always work as GPIOs */
+ if (ofs == 0 || ofs == 3)
+ return !gpio_en ? -EINVAL : 0;
+
+ if (ofs == 8 && !gpio_en)
+ val = MV_GPIO_FUNC_TX_DIS_REG;
+ else
+ val = !!gpio_en;
+
+ reg = MV_GPIO_INT_TYPE_REG(ofs);
+ gpio->cache_int_type[reg] &= ~MV_GPIO_FUNC_MASK(ofs);
+ gpio->cache_int_type[reg] |= MV_GPIO_FUNC_PREP(ofs, val);
+
+ gpio->cache_int_type[reg] |= MV_GPIO_INT_UPD_FLAG;
+
+ return 0;
+}
+
+static int mv2222_gpio_cache_int_type(struct mv2222_gpio *gpio,
+ unsigned int ofs, unsigned int type)
+{
+ u16 val;
+ int reg;
+
+ if (!mv2222_gpio_offset_is_valid(ofs))
+ return -EINVAL;
+
+ switch (type) {
+ case IRQ_TYPE_NONE:
+ val = MV_GPIO_INT_NO_IRQ;
+ break;
+ case IRQ_TYPE_EDGE_RISING:
+ val = MV_GPIO_INT_EDGE_RISING;
+ break;
+ case IRQ_TYPE_EDGE_FALLING:
+ val = MV_GPIO_INT_EDGE_FALLING;
+ break;
+ case IRQ_TYPE_EDGE_BOTH:
+ val = MV_GPIO_INT_EDGE_BOTH;
+ break;
+ case IRQ_TYPE_LEVEL_HIGH:
+ val = MV_GPIO_INT_LEVEL_HIGH;
+ break;
+ case IRQ_TYPE_LEVEL_LOW:
+ val = MV_GPIO_INT_LEVEL_LOW;
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ reg = MV_GPIO_INT_TYPE_REG(ofs);
+ gpio->cache_int_type[reg] &= ~MV_GPIO_INT_TYPE_MASK(ofs);
+ gpio->cache_int_type[reg] |= MV_GPIO_INT_TYPE_PREP(ofs, val);
+
+ gpio->cache_int_type[reg] |= MV_GPIO_INT_UPD_FLAG;
+
+ return 0;
+}
+
+static int mv2222_gpio_cache_int_flush(struct mv2222_gpio *gpio)
+{
+ int i, ret;
+
+ if (gpio->cache_int_en & MV_GPIO_INT_UPD_FLAG) {
+ ret = phy_write_mmd(gpio->phydev, MDIO_MMD_VEND2,
+ MV_GPIO_INT_EN, gpio->cache_int_en);
+ if (ret < 0)
+ return ret;
+
+ gpio->cache_int_en &= ~MV_GPIO_INT_UPD_FLAG;
+ }
+
+ for (i = 0; i < ARRAY_SIZE(gpio->cache_int_type); ++i) {
+ if (!(gpio->cache_int_type[i] & MV_GPIO_INT_UPD_FLAG))
+ continue;
+
+ ret = phy_write_mmd(gpio->phydev, MDIO_MMD_VEND2,
+ MV_GPIO_INT_TYPE1 + i, gpio->cache_int_type[i]);
+ if (ret < 0)
+ return ret;
+
+ gpio->cache_int_type[i] &= ~MV_GPIO_INT_UPD_FLAG;
+ }
+
+ return 0;
+}
+
+static int mv2222_gpio_init(struct mv2222_gpio *gpio)
+{
+ int i, ret;
+
+ /* Setup GPIO function and default IRQs state for all valid GPIOs */
+ for (i = 0; i < MV_GPIO_NUM; ++i) {
+ mv2222_gpio_cache_int_en(gpio, i, false);
+
+ if (gpio->cache_val_mask & BIT(i))
+ mv2222_gpio_cache_func(gpio, i, true);
+ else
+ mv2222_gpio_cache_func(gpio, i, false);
+
+ mv2222_gpio_cache_int_type(gpio, i, IRQ_TYPE_NONE);
+ }
+
+ ret = mv2222_gpio_cache_int_flush(gpio);
+ if (ret < 0)
+ return ret;
+
+ ret = mv2222_gpio_read_tri_ctrl(gpio);
+ if (ret < 0)
+ return ret;
+
+ ret = mv2222_gpio_read_data(gpio);
+ if (ret < 0)
+ return ret;
+
+ return 0;
+}
+
+static int mv2222_gpio_get_direction(struct gpio_chip *gc, unsigned int ofs)
+{
+ struct mv2222_gpio *gpio = gpiochip_get_data(gc);
+ int ret;
+
+ mutex_lock(&gpio->cache_lock);
+ ret = !(gpio->cache_tri_ctrl & BIT(ofs));
+ mutex_unlock(&gpio->cache_lock);
+
+ return ret;
+}
+
+static int mv2222_gpio_direction_input(struct gpio_chip *gc, unsigned int ofs)
+{
+ struct mv2222_gpio *gpio = gpiochip_get_data(gc);
+ u16 mask = BIT(ofs);
+
+ return mv2222_gpio_wthru_tri_ctrl(gpio, mask, 0);
+}
+
+static int mv2222_gpio_direction_output(struct gpio_chip *gc,
+ unsigned int ofs, int val)
+{
+ struct mv2222_gpio *gpio = gpiochip_get_data(gc);
+ u16 mask = BIT(ofs);
+ int ret;
+
+ ret = mv2222_gpio_wthru_data(gpio, mask, val ? mask : 0);
+ if (ret < 0)
+ return ret;
+
+ return mv2222_gpio_wthru_tri_ctrl(gpio, mask, mask);
+}
+
+static int mv2222_gpio_get(struct gpio_chip *gc, unsigned int ofs)
+{
+ struct mv2222_gpio *gpio = gpiochip_get_data(gc);
+ u16 mask = BIT(ofs);
+ int val;
+
+ val = mv2222_gpio_read_data(gpio);
+ if (val < 0)
+ return val;
+
+ return !!(val & mask);
+}
+
+static int mv2222_gpio_get_multiple(struct gpio_chip *gc,
+ unsigned long *mask, unsigned long *bits)
+{
+ struct mv2222_gpio *gpio = gpiochip_get_data(gc);
+ int val;
+
+ val = mv2222_gpio_read_data(gpio);
+ if (val < 0)
+ return val;
+
+ *bits &= ~*mask;
+ *bits |= val & *mask;
+
+ return 0;
+}
+
+static int mv2222_gpio_set(struct gpio_chip *gc, unsigned ofs, int val)
+{
+ struct mv2222_gpio *gpio = gpiochip_get_data(gc);
+ u16 mask = BIT(ofs);
+ int ret;
+
+ ret = mv2222_gpio_wthru_data(gpio, mask, val ? mask : 0);
+ if (ret < 0)
+ phydev_err(gpio->phydev, "Failed to set GPIO %d\n", ofs);
+
+ return ret;
+}
+
+static int mv2222_gpio_set_multiple(struct gpio_chip *gc,
+ unsigned long *mask, unsigned long *bits)
+{
+ struct mv2222_gpio *gpio = gpiochip_get_data(gc);
+ int ret;
+
+ ret = mv2222_gpio_wthru_data(gpio, *mask, *bits);
+ if (ret < 0)
+ phydev_err(gpio->phydev, "Failed to set GPIOs 0x%04lx\n", *bits);
+
+ return ret;
+}
+
+static int mv2222_gpio_set_config(struct gpio_chip *gc, unsigned int ofs,
+ unsigned long cfg)
+{
+ enum pin_config_param mode = pinconf_to_config_param(cfg);
+
+ /* All output pins operate as open drain */
+ if (mode != PIN_CONFIG_DRIVE_OPEN_DRAIN)
+ return -ENOTSUPP;
+
+ return 0;
+}
+
+static int mv2222_gpio_init_valid_mask(struct gpio_chip *gc,
+ unsigned long *valid_mask,
+ unsigned int ngpios)
+{
+ struct mv2222_gpio *gpio = gpiochip_get_data(gc);
+ int ret;
+
+ if (ngpios > MV_GPIO_NUM)
+ return -EINVAL;
+
+ *valid_mask &= MV_GPIO_VAL_MASK;
+
+ gpio->cache_val_mask = *valid_mask;
+
+ ret = mv2222_gpio_init(gpio);
+ if (ret < 0)
+ return ret;
+
+ return 0;
+}
+
+static void mv2222_gpio_irq_mask(struct irq_data *d)
+{
+ struct gpio_chip *gc = irq_data_get_irq_chip_data(d);
+ struct mv2222_gpio *gpio = gpiochip_get_data(gc);
+ irq_hw_number_t hwirq = irqd_to_hwirq(d);
+
+ mv2222_gpio_cache_int_en(gpio, hwirq, false);
+ gpiochip_disable_irq(gc, hwirq);
+}
+
+static void mv2222_gpio_irq_unmask(struct irq_data *d)
+{
+ struct gpio_chip *gc = irq_data_get_irq_chip_data(d);
+ struct mv2222_gpio *gpio = gpiochip_get_data(gc);
+ irq_hw_number_t hwirq = irqd_to_hwirq(d);
+
+ gpiochip_enable_irq(gc, hwirq);
+ mv2222_gpio_cache_int_en(gpio, hwirq, true);
+}
+
+static int mv2222_gpio_irq_set_type(struct irq_data *d, unsigned int type)
+{
+ struct gpio_chip *gc = irq_data_get_irq_chip_data(d);
+ struct mv2222_gpio *gpio = gpiochip_get_data(gc);
+ irq_hw_number_t hwirq = irqd_to_hwirq(d);
+
+ return mv2222_gpio_cache_int_type(gpio, hwirq, type);
+}
+
+static int mv2222_gpio_irq_set_wake(struct irq_data *d, unsigned int on)
+{
+ struct gpio_chip *gc = irq_data_get_irq_chip_data(d);
+ struct mv2222_gpio *gpio = gpiochip_get_data(gc);
+
+ return irq_set_irq_wake(gpio->irq, on);
+}
+
+static void mv2222_gpio_irq_bus_lock(struct irq_data *d)
+{
+ struct gpio_chip *gc = irq_data_get_irq_chip_data(d);
+ struct mv2222_gpio *gpio = gpiochip_get_data(gc);
+
+ mutex_lock(&gpio->cache_lock);
+}
+
+static void mv2222_gpio_irq_bus_sync_unlock(struct irq_data *d)
+{
+ struct gpio_chip *gc = irq_data_get_irq_chip_data(d);
+ struct mv2222_gpio *gpio = gpiochip_get_data(gc);
+ int ret;
+
+ ret = mv2222_gpio_cache_int_flush(gpio);
+ if (ret < 0)
+ phydev_err(gpio->phydev, "Failed to flush GPIO IRQs state\n");
+
+ mutex_unlock(&gpio->cache_lock);
+}
+
+static void mv2222_gpio_irq_print_chip(struct irq_data *d, struct seq_file *p)
+{
+ struct gpio_chip *gc = irq_data_get_irq_chip_data(d);
+
+ seq_printf(p, dev_name(gc->parent));
+}
+
+static const struct irq_chip mv2222_gpio_irq_chip = {
+ .name = "mv88x2222",
+ .irq_mask = mv2222_gpio_irq_mask,
+ .irq_unmask = mv2222_gpio_irq_unmask,
+ .irq_set_wake = mv2222_gpio_irq_set_wake,
+ .irq_set_type = mv2222_gpio_irq_set_type,
+ .irq_bus_lock = mv2222_gpio_irq_bus_lock,
+ .irq_bus_sync_unlock = mv2222_gpio_irq_bus_sync_unlock,
+ .irq_print_chip = mv2222_gpio_irq_print_chip,
+ .flags = IRQCHIP_IMMUTABLE,
+ GPIOCHIP_IRQ_RESOURCE_HELPERS,
+};
+
+static irqreturn_t mv2222_gpio_handle_interrupt(int irq, void *devid)
+{
+ struct mv2222_gpio *gpio = devid;
+ unsigned long pending;
+ int i, val;
+
+ val = phy_read_mmd(gpio->phydev, MDIO_MMD_VEND2, MV_GPIO_INT_STAT);
+ if (val < 0)
+ return IRQ_NONE;
+
+ /* The interrupt status register exports the raw IRQ status */
+ mutex_lock(&gpio->cache_lock);
+ pending = val & gpio->cache_int_en;
+ mutex_unlock(&gpio->cache_lock);
+
+ for_each_set_bit(i, &pending, gpio->gc.ngpio)
+ handle_nested_irq(irq_find_mapping(gpio->gc.irq.domain, i));
+
+ return IRQ_RETVAL(pending);
+}
+
+static const char * const mv2222_gpio_names[MV_GPIO_NUM] = {
+ [MV_88X2222_MOD_ABS] = "MOD_ABS",
+ [MV_88X2222_TX_FAULT] = "TX_FAULT",
+ [MV_88X2222_RX_LOS] = "RX_LOS",
+ [MV_88X2222_GPIO] = "GPIO",
+ [MV_88X2222_LED0] = "LED0",
+ [MV_88X2222_LED1] = "LED1",
+ [MV_88X2222_MPC] = "MPC",
+ [MV_88X2222_TOD] = "TOD",
+ [MV_88X2222_TX_DISABLE] = "TX_DISABLE",
+ [MV_88X2222_UNDEF] = NULL,
+ [MV_88X2222_SDA] = "SDA",
+ [MV_88X2222_SCL] = "SCL",
+};
+
+static int mv2222_gpio_probe(struct phy_device *phydev)
+{
+ struct device *dev = &phydev->mdio.dev;
+ struct mv2222_gpio *gpio;
+ struct gpio_chip *gc;
+ int ret;
+
+ /*
+ * No GPIO-chip registration if the PHY-device isn't marked as a
+ * GPIO-controller. This is another level of protection for the
+ * backward compatibility in case if the platforms rely on the
+ * default/pre-initialized pins functions.
+ */
+ if (!device_property_present(dev, "gpio-controller"))
+ return 0;
+
+ /*
+ * Marvell 88x2222 GPIO CSRs are tolerant to the soft-resets. They are
+ * marked as "Retain" in the "SW Rst" calumn of the registers
+ * description table defined in the HW-databook. On the contrary the
+ * hard-reset will reset all the GPIO CSRs to their default states which
+ * in its turn will break the driver GPIO functionality for sure.
+ */
+ if (phydev->mdio.reset_gpio || phydev->mdio.reset_ctrl) {
+ phydev_warn(phydev, "Hard-reset detected, GPIOs unsupported\n");
+ return 0;
+ }
+
+ gpio = devm_kzalloc(dev, sizeof(*gpio), GFP_KERNEL);
+ if (!gpio)
+ return -ENOMEM;
+
+ gpio->phydev = phydev;
+
+ mutex_init(&gpio->cache_lock);
+
+ gc = &gpio->gc;
+ gc->label = "mv88x2222";
+ gc->parent = dev;
+ gc->owner = THIS_MODULE;
+ gc->get_direction = mv2222_gpio_get_direction;
+ gc->direction_input = mv2222_gpio_direction_input;
+ gc->direction_output = mv2222_gpio_direction_output;
+ gc->get = mv2222_gpio_get;
+ gc->get_multiple = mv2222_gpio_get_multiple;
+ gc->set = mv2222_gpio_set;
+ gc->set_multiple = mv2222_gpio_set_multiple;
+ gc->set_config = mv2222_gpio_set_config;
+ gc->init_valid_mask = mv2222_gpio_init_valid_mask;
+ gc->base = -1;
+ gc->ngpio = MV_GPIO_NUM;
+ gc->names = mv2222_gpio_names;
+ gc->can_sleep = true;
+
+ if (phy_interrupt_is_valid(phydev)) {
+ struct gpio_irq_chip *girq = &gc->irq;
+
+ gpio->irq = phydev->irq;
+
+ girq->handler = handle_bad_irq;
+ girq->default_type = IRQ_TYPE_NONE;
+ girq->parent_handler = NULL;
+ girq->num_parents = 0;
+ girq->parents = NULL;
+ girq->threaded = true;
+
+ gpio_irq_chip_set_chip(girq, &mv2222_gpio_irq_chip);
+
+ ret = devm_request_threaded_irq(dev, gpio->irq, NULL,
+ mv2222_gpio_handle_interrupt,
+ IRQF_ONESHOT | IRQF_SHARED,
+ phydev_name(phydev), gpio);
+ if (ret) {
+ phydev_err(phydev, "Failed to request GPIO IRQ\n");
+ return ret;
+ }
+ } else {
+ gpio->irq = IRQ_NOTCONNECTED;
+ }
+
+ ret = devm_gpiochip_add_data(dev, gc, gpio);
+ if (ret)
+ phydev_err(phydev, "Failed to register GPIO chip\n");
+
+ return ret;
+}
+#else
+static int mv2222_gpio_probe(struct phy_device *phydev)
+{
+ return 0;
+}
+#endif /* !CONFIG_MARVELL_88X2222_GPIO */
+
static int mv2222_configure_serdes(struct phy_port *port, bool enable,
phy_interface_t interface)
{
@@ -998,6 +1651,7 @@ static int mv2222_probe(struct phy_device *phydev)
{
struct device *dev = &phydev->mdio.dev;
struct mv2222_data *priv = NULL;
+ int ret;
__ETHTOOL_DECLARE_LINK_MODE_MASK(supported) = { 0, };
@@ -1033,6 +1687,10 @@ static int mv2222_probe(struct phy_device *phydev)
priv->line_interface = PHY_INTERFACE_MODE_NA;
phydev->priv = priv;
+ ret = mv2222_gpio_probe(phydev);
+ if (ret)
+ return ret;
+
return 0;
}