mirror of
				git://git.openwrt.org/openwrt/openwrt.git
				synced 2025-10-31 05:54:26 -04:00 
			
		
		
		
	Backport qca808x LED support patch merged upstream needed to drop handling of it from the SSDK for ipq807x target. Signed-off-by: Christian Marangi <ansuelsmth@gmail.com>
		
			
				
	
	
		
			409 lines
		
	
	
		
			13 KiB
		
	
	
	
		
			Diff
		
	
	
	
	
	
			
		
		
	
	
			409 lines
		
	
	
		
			13 KiB
		
	
	
	
		
			Diff
		
	
	
	
	
	
| From 7196062b64ee470b91015f3d2e82d225948258ea Mon Sep 17 00:00:00 2001
 | |
| From: Christian Marangi <ansuelsmth@gmail.com>
 | |
| Date: Thu, 25 Jan 2024 21:37:01 +0100
 | |
| Subject: [PATCH 5/5] net: phy: at803x: add LED support for qca808x
 | |
| 
 | |
| Add LED support for QCA8081 PHY.
 | |
| 
 | |
| Documentation for this LEDs PHY is very scarce even with NDA access
 | |
| to Documentation for OEMs. Only the blink pattern are documented and are
 | |
| very confusing most of the time. No documentation is present about
 | |
| forcing the LED on/off or to always blink.
 | |
| 
 | |
| Those settings were reversed by poking the regs and trying to find the
 | |
| correct bits to trigger these modes. Some bits mode are not clear and
 | |
| maybe the documentation option are not 100% correct. For the sake of LED
 | |
| support the reversed option are enough to add support for current LED
 | |
| APIs.
 | |
| 
 | |
| Supported HW control modes are:
 | |
| - tx
 | |
| - rx
 | |
| - link_10
 | |
| - link_100
 | |
| - link_1000
 | |
| - link_2500
 | |
| - half_duplex
 | |
| - full_duplex
 | |
| 
 | |
| Also add support for LED polarity set to set LED polarity to active
 | |
| high or low. QSDK sets this value to high by default but PHY reset value
 | |
| doesn't have this enabled by default.
 | |
| 
 | |
| QSDK also sets 2 additional bits but their usage is not clear, info about
 | |
| this is added in the header. It was verified that for correct function
 | |
| of the LED if active high is needed, only BIT 6 is needed.
 | |
| 
 | |
| Signed-off-by: Christian Marangi <ansuelsmth@gmail.com>
 | |
| Reviewed-by: Andrew Lunn <andrew@lunn.ch>
 | |
| Link: https://lore.kernel.org/r/20240125203702.4552-6-ansuelsmth@gmail.com
 | |
| Signed-off-by: Jakub Kicinski <kuba@kernel.org>
 | |
| ---
 | |
|  drivers/net/phy/at803x.c | 327 +++++++++++++++++++++++++++++++++++++++
 | |
|  1 file changed, 327 insertions(+)
 | |
| 
 | |
| --- a/drivers/net/phy/at803x.c
 | |
| +++ b/drivers/net/phy/at803x.c
 | |
| @@ -301,6 +301,87 @@
 | |
|  /* Added for reference of existence but should be handled by wait_for_completion already */
 | |
|  #define QCA808X_CDT_STATUS_STAT_BUSY		(BIT(1) | BIT(3))
 | |
|  
 | |
| +#define QCA808X_MMD7_LED_GLOBAL			0x8073
 | |
| +#define QCA808X_LED_BLINK_1			GENMASK(11, 6)
 | |
| +#define QCA808X_LED_BLINK_2			GENMASK(5, 0)
 | |
| +/* Values are the same for both BLINK_1 and BLINK_2 */
 | |
| +#define QCA808X_LED_BLINK_FREQ_MASK		GENMASK(5, 3)
 | |
| +#define QCA808X_LED_BLINK_FREQ_2HZ		FIELD_PREP(QCA808X_LED_BLINK_FREQ_MASK, 0x0)
 | |
| +#define QCA808X_LED_BLINK_FREQ_4HZ		FIELD_PREP(QCA808X_LED_BLINK_FREQ_MASK, 0x1)
 | |
| +#define QCA808X_LED_BLINK_FREQ_8HZ		FIELD_PREP(QCA808X_LED_BLINK_FREQ_MASK, 0x2)
 | |
| +#define QCA808X_LED_BLINK_FREQ_16HZ		FIELD_PREP(QCA808X_LED_BLINK_FREQ_MASK, 0x3)
 | |
| +#define QCA808X_LED_BLINK_FREQ_32HZ		FIELD_PREP(QCA808X_LED_BLINK_FREQ_MASK, 0x4)
 | |
| +#define QCA808X_LED_BLINK_FREQ_64HZ		FIELD_PREP(QCA808X_LED_BLINK_FREQ_MASK, 0x5)
 | |
| +#define QCA808X_LED_BLINK_FREQ_128HZ		FIELD_PREP(QCA808X_LED_BLINK_FREQ_MASK, 0x6)
 | |
| +#define QCA808X_LED_BLINK_FREQ_256HZ		FIELD_PREP(QCA808X_LED_BLINK_FREQ_MASK, 0x7)
 | |
| +#define QCA808X_LED_BLINK_DUTY_MASK		GENMASK(2, 0)
 | |
| +#define QCA808X_LED_BLINK_DUTY_50_50		FIELD_PREP(QCA808X_LED_BLINK_DUTY_MASK, 0x0)
 | |
| +#define QCA808X_LED_BLINK_DUTY_75_25		FIELD_PREP(QCA808X_LED_BLINK_DUTY_MASK, 0x1)
 | |
| +#define QCA808X_LED_BLINK_DUTY_25_75		FIELD_PREP(QCA808X_LED_BLINK_DUTY_MASK, 0x2)
 | |
| +#define QCA808X_LED_BLINK_DUTY_33_67		FIELD_PREP(QCA808X_LED_BLINK_DUTY_MASK, 0x3)
 | |
| +#define QCA808X_LED_BLINK_DUTY_67_33		FIELD_PREP(QCA808X_LED_BLINK_DUTY_MASK, 0x4)
 | |
| +#define QCA808X_LED_BLINK_DUTY_17_83		FIELD_PREP(QCA808X_LED_BLINK_DUTY_MASK, 0x5)
 | |
| +#define QCA808X_LED_BLINK_DUTY_83_17		FIELD_PREP(QCA808X_LED_BLINK_DUTY_MASK, 0x6)
 | |
| +#define QCA808X_LED_BLINK_DUTY_8_92		FIELD_PREP(QCA808X_LED_BLINK_DUTY_MASK, 0x7)
 | |
| +
 | |
| +#define QCA808X_MMD7_LED2_CTRL			0x8074
 | |
| +#define QCA808X_MMD7_LED2_FORCE_CTRL		0x8075
 | |
| +#define QCA808X_MMD7_LED1_CTRL			0x8076
 | |
| +#define QCA808X_MMD7_LED1_FORCE_CTRL		0x8077
 | |
| +#define QCA808X_MMD7_LED0_CTRL			0x8078
 | |
| +#define QCA808X_MMD7_LED_CTRL(x)		(0x8078 - ((x) * 2))
 | |
| +
 | |
| +/* LED hw control pattern is the same for every LED */
 | |
| +#define QCA808X_LED_PATTERN_MASK		GENMASK(15, 0)
 | |
| +#define QCA808X_LED_SPEED2500_ON		BIT(15)
 | |
| +#define QCA808X_LED_SPEED2500_BLINK		BIT(14)
 | |
| +/* Follow blink trigger even if duplex or speed condition doesn't match */
 | |
| +#define QCA808X_LED_BLINK_CHECK_BYPASS		BIT(13)
 | |
| +#define QCA808X_LED_FULL_DUPLEX_ON		BIT(12)
 | |
| +#define QCA808X_LED_HALF_DUPLEX_ON		BIT(11)
 | |
| +#define QCA808X_LED_TX_BLINK			BIT(10)
 | |
| +#define QCA808X_LED_RX_BLINK			BIT(9)
 | |
| +#define QCA808X_LED_TX_ON_10MS			BIT(8)
 | |
| +#define QCA808X_LED_RX_ON_10MS			BIT(7)
 | |
| +#define QCA808X_LED_SPEED1000_ON		BIT(6)
 | |
| +#define QCA808X_LED_SPEED100_ON			BIT(5)
 | |
| +#define QCA808X_LED_SPEED10_ON			BIT(4)
 | |
| +#define QCA808X_LED_COLLISION_BLINK		BIT(3)
 | |
| +#define QCA808X_LED_SPEED1000_BLINK		BIT(2)
 | |
| +#define QCA808X_LED_SPEED100_BLINK		BIT(1)
 | |
| +#define QCA808X_LED_SPEED10_BLINK		BIT(0)
 | |
| +
 | |
| +#define QCA808X_MMD7_LED0_FORCE_CTRL		0x8079
 | |
| +#define QCA808X_MMD7_LED_FORCE_CTRL(x)		(0x8079 - ((x) * 2))
 | |
| +
 | |
| +/* LED force ctrl is the same for every LED
 | |
| + * No documentation exist for this, not even internal one
 | |
| + * with NDA as QCOM gives only info about configuring
 | |
| + * hw control pattern rules and doesn't indicate any way
 | |
| + * to force the LED to specific mode.
 | |
| + * These define comes from reverse and testing and maybe
 | |
| + * lack of some info or some info are not entirely correct.
 | |
| + * For the basic LED control and hw control these finding
 | |
| + * are enough to support LED control in all the required APIs.
 | |
| + *
 | |
| + * On doing some comparison with implementation with qca807x,
 | |
| + * it was found that it's 1:1 equal to it and confirms all the
 | |
| + * reverse done. It was also found further specification with the
 | |
| + * force mode and the blink modes.
 | |
| + */
 | |
| +#define QCA808X_LED_FORCE_EN			BIT(15)
 | |
| +#define QCA808X_LED_FORCE_MODE_MASK		GENMASK(14, 13)
 | |
| +#define QCA808X_LED_FORCE_BLINK_1		FIELD_PREP(QCA808X_LED_FORCE_MODE_MASK, 0x3)
 | |
| +#define QCA808X_LED_FORCE_BLINK_2		FIELD_PREP(QCA808X_LED_FORCE_MODE_MASK, 0x2)
 | |
| +#define QCA808X_LED_FORCE_ON			FIELD_PREP(QCA808X_LED_FORCE_MODE_MASK, 0x1)
 | |
| +#define QCA808X_LED_FORCE_OFF			FIELD_PREP(QCA808X_LED_FORCE_MODE_MASK, 0x0)
 | |
| +
 | |
| +#define QCA808X_MMD7_LED_POLARITY_CTRL		0x901a
 | |
| +/* QSDK sets by default 0x46 to this reg that sets BIT 6 for
 | |
| + * LED to active high. It's not clear what BIT 3 and BIT 4 does.
 | |
| + */
 | |
| +#define QCA808X_LED_ACTIVE_HIGH			BIT(6)
 | |
| +
 | |
|  /* QCA808X 1G chip type */
 | |
|  #define QCA808X_PHY_MMD7_CHIP_TYPE		0x901d
 | |
|  #define QCA808X_PHY_CHIP_TYPE_1G		BIT(0)
 | |
| @@ -346,6 +427,7 @@ struct at803x_priv {
 | |
|  	struct regulator_dev *vddio_rdev;
 | |
|  	struct regulator_dev *vddh_rdev;
 | |
|  	u64 stats[ARRAY_SIZE(qca83xx_hw_stats)];
 | |
| +	int led_polarity_mode;
 | |
|  };
 | |
|  
 | |
|  struct at803x_context {
 | |
| @@ -706,6 +788,9 @@ static int at803x_probe(struct phy_devic
 | |
|  	if (!priv)
 | |
|  		return -ENOMEM;
 | |
|  
 | |
| +	/* Init LED polarity mode to -1 */
 | |
| +	priv->led_polarity_mode = -1;
 | |
| +
 | |
|  	phydev->priv = priv;
 | |
|  
 | |
|  	ret = at803x_parse_dt(phydev);
 | |
| @@ -2235,6 +2320,242 @@ static void qca808x_link_change_notify(s
 | |
|  				   phydev->link ? QCA8081_PHY_FIFO_RSTN : 0);
 | |
|  }
 | |
|  
 | |
| +static int qca808x_led_parse_netdev(struct phy_device *phydev, unsigned long rules,
 | |
| +				    u16 *offload_trigger)
 | |
| +{
 | |
| +	/* Parsing specific to netdev trigger */
 | |
| +	if (test_bit(TRIGGER_NETDEV_TX, &rules))
 | |
| +		*offload_trigger |= QCA808X_LED_TX_BLINK;
 | |
| +	if (test_bit(TRIGGER_NETDEV_RX, &rules))
 | |
| +		*offload_trigger |= QCA808X_LED_RX_BLINK;
 | |
| +	if (test_bit(TRIGGER_NETDEV_LINK_10, &rules))
 | |
| +		*offload_trigger |= QCA808X_LED_SPEED10_ON;
 | |
| +	if (test_bit(TRIGGER_NETDEV_LINK_100, &rules))
 | |
| +		*offload_trigger |= QCA808X_LED_SPEED100_ON;
 | |
| +	if (test_bit(TRIGGER_NETDEV_LINK_1000, &rules))
 | |
| +		*offload_trigger |= QCA808X_LED_SPEED1000_ON;
 | |
| +	if (test_bit(TRIGGER_NETDEV_LINK_2500, &rules))
 | |
| +		*offload_trigger |= QCA808X_LED_SPEED2500_ON;
 | |
| +	if (test_bit(TRIGGER_NETDEV_HALF_DUPLEX, &rules))
 | |
| +		*offload_trigger |= QCA808X_LED_HALF_DUPLEX_ON;
 | |
| +	if (test_bit(TRIGGER_NETDEV_FULL_DUPLEX, &rules))
 | |
| +		*offload_trigger |= QCA808X_LED_FULL_DUPLEX_ON;
 | |
| +
 | |
| +	if (rules && !*offload_trigger)
 | |
| +		return -EOPNOTSUPP;
 | |
| +
 | |
| +	/* Enable BLINK_CHECK_BYPASS by default to make the LED
 | |
| +	 * blink even with duplex or speed mode not enabled.
 | |
| +	 */
 | |
| +	*offload_trigger |= QCA808X_LED_BLINK_CHECK_BYPASS;
 | |
| +
 | |
| +	return 0;
 | |
| +}
 | |
| +
 | |
| +static int qca808x_led_hw_control_enable(struct phy_device *phydev, u8 index)
 | |
| +{
 | |
| +	u16 reg;
 | |
| +
 | |
| +	if (index > 2)
 | |
| +		return -EINVAL;
 | |
| +
 | |
| +	reg = QCA808X_MMD7_LED_FORCE_CTRL(index);
 | |
| +
 | |
| +	return phy_clear_bits_mmd(phydev, MDIO_MMD_AN, reg,
 | |
| +				  QCA808X_LED_FORCE_EN);
 | |
| +}
 | |
| +
 | |
| +static int qca808x_led_hw_is_supported(struct phy_device *phydev, u8 index,
 | |
| +				       unsigned long rules)
 | |
| +{
 | |
| +	u16 offload_trigger = 0;
 | |
| +
 | |
| +	if (index > 2)
 | |
| +		return -EINVAL;
 | |
| +
 | |
| +	return qca808x_led_parse_netdev(phydev, rules, &offload_trigger);
 | |
| +}
 | |
| +
 | |
| +static int qca808x_led_hw_control_set(struct phy_device *phydev, u8 index,
 | |
| +				      unsigned long rules)
 | |
| +{
 | |
| +	u16 reg, offload_trigger = 0;
 | |
| +	int ret;
 | |
| +
 | |
| +	if (index > 2)
 | |
| +		return -EINVAL;
 | |
| +
 | |
| +	reg = QCA808X_MMD7_LED_CTRL(index);
 | |
| +
 | |
| +	ret = qca808x_led_parse_netdev(phydev, rules, &offload_trigger);
 | |
| +	if (ret)
 | |
| +		return ret;
 | |
| +
 | |
| +	ret = qca808x_led_hw_control_enable(phydev, index);
 | |
| +	if (ret)
 | |
| +		return ret;
 | |
| +
 | |
| +	return phy_modify_mmd(phydev, MDIO_MMD_AN, reg,
 | |
| +			      QCA808X_LED_PATTERN_MASK,
 | |
| +			      offload_trigger);
 | |
| +}
 | |
| +
 | |
| +static bool qca808x_led_hw_control_status(struct phy_device *phydev, u8 index)
 | |
| +{
 | |
| +	u16 reg;
 | |
| +	int val;
 | |
| +
 | |
| +	if (index > 2)
 | |
| +		return false;
 | |
| +
 | |
| +	reg = QCA808X_MMD7_LED_FORCE_CTRL(index);
 | |
| +
 | |
| +	val = phy_read_mmd(phydev, MDIO_MMD_AN, reg);
 | |
| +
 | |
| +	return !(val & QCA808X_LED_FORCE_EN);
 | |
| +}
 | |
| +
 | |
| +static int qca808x_led_hw_control_get(struct phy_device *phydev, u8 index,
 | |
| +				      unsigned long *rules)
 | |
| +{
 | |
| +	u16 reg;
 | |
| +	int val;
 | |
| +
 | |
| +	if (index > 2)
 | |
| +		return -EINVAL;
 | |
| +
 | |
| +	/* Check if we have hw control enabled */
 | |
| +	if (qca808x_led_hw_control_status(phydev, index))
 | |
| +		return -EINVAL;
 | |
| +
 | |
| +	reg = QCA808X_MMD7_LED_CTRL(index);
 | |
| +
 | |
| +	val = phy_read_mmd(phydev, MDIO_MMD_AN, reg);
 | |
| +	if (val & QCA808X_LED_TX_BLINK)
 | |
| +		set_bit(TRIGGER_NETDEV_TX, rules);
 | |
| +	if (val & QCA808X_LED_RX_BLINK)
 | |
| +		set_bit(TRIGGER_NETDEV_RX, rules);
 | |
| +	if (val & QCA808X_LED_SPEED10_ON)
 | |
| +		set_bit(TRIGGER_NETDEV_LINK_10, rules);
 | |
| +	if (val & QCA808X_LED_SPEED100_ON)
 | |
| +		set_bit(TRIGGER_NETDEV_LINK_100, rules);
 | |
| +	if (val & QCA808X_LED_SPEED1000_ON)
 | |
| +		set_bit(TRIGGER_NETDEV_LINK_1000, rules);
 | |
| +	if (val & QCA808X_LED_SPEED2500_ON)
 | |
| +		set_bit(TRIGGER_NETDEV_LINK_2500, rules);
 | |
| +	if (val & QCA808X_LED_HALF_DUPLEX_ON)
 | |
| +		set_bit(TRIGGER_NETDEV_HALF_DUPLEX, rules);
 | |
| +	if (val & QCA808X_LED_FULL_DUPLEX_ON)
 | |
| +		set_bit(TRIGGER_NETDEV_FULL_DUPLEX, rules);
 | |
| +
 | |
| +	return 0;
 | |
| +}
 | |
| +
 | |
| +static int qca808x_led_hw_control_reset(struct phy_device *phydev, u8 index)
 | |
| +{
 | |
| +	u16 reg;
 | |
| +
 | |
| +	if (index > 2)
 | |
| +		return -EINVAL;
 | |
| +
 | |
| +	reg = QCA808X_MMD7_LED_CTRL(index);
 | |
| +
 | |
| +	return phy_clear_bits_mmd(phydev, MDIO_MMD_AN, reg,
 | |
| +				  QCA808X_LED_PATTERN_MASK);
 | |
| +}
 | |
| +
 | |
| +static int qca808x_led_brightness_set(struct phy_device *phydev,
 | |
| +				      u8 index, enum led_brightness value)
 | |
| +{
 | |
| +	u16 reg;
 | |
| +	int ret;
 | |
| +
 | |
| +	if (index > 2)
 | |
| +		return -EINVAL;
 | |
| +
 | |
| +	if (!value) {
 | |
| +		ret = qca808x_led_hw_control_reset(phydev, index);
 | |
| +		if (ret)
 | |
| +			return ret;
 | |
| +	}
 | |
| +
 | |
| +	reg = QCA808X_MMD7_LED_FORCE_CTRL(index);
 | |
| +
 | |
| +	return phy_modify_mmd(phydev, MDIO_MMD_AN, reg,
 | |
| +			      QCA808X_LED_FORCE_EN | QCA808X_LED_FORCE_MODE_MASK,
 | |
| +			      QCA808X_LED_FORCE_EN | value ? QCA808X_LED_FORCE_ON :
 | |
| +							     QCA808X_LED_FORCE_OFF);
 | |
| +}
 | |
| +
 | |
| +static int qca808x_led_blink_set(struct phy_device *phydev, u8 index,
 | |
| +				 unsigned long *delay_on,
 | |
| +				 unsigned long *delay_off)
 | |
| +{
 | |
| +	int ret;
 | |
| +	u16 reg;
 | |
| +
 | |
| +	if (index > 2)
 | |
| +		return -EINVAL;
 | |
| +
 | |
| +	reg = QCA808X_MMD7_LED_FORCE_CTRL(index);
 | |
| +
 | |
| +	/* Set blink to 50% off, 50% on at 4Hz by default */
 | |
| +	ret = phy_modify_mmd(phydev, MDIO_MMD_AN, QCA808X_MMD7_LED_GLOBAL,
 | |
| +			     QCA808X_LED_BLINK_FREQ_MASK | QCA808X_LED_BLINK_DUTY_MASK,
 | |
| +			     QCA808X_LED_BLINK_FREQ_4HZ | QCA808X_LED_BLINK_DUTY_50_50);
 | |
| +	if (ret)
 | |
| +		return ret;
 | |
| +
 | |
| +	/* We use BLINK_1 for normal blinking */
 | |
| +	ret = phy_modify_mmd(phydev, MDIO_MMD_AN, reg,
 | |
| +			     QCA808X_LED_FORCE_EN | QCA808X_LED_FORCE_MODE_MASK,
 | |
| +			     QCA808X_LED_FORCE_EN | QCA808X_LED_FORCE_BLINK_1);
 | |
| +	if (ret)
 | |
| +		return ret;
 | |
| +
 | |
| +	/* We set blink to 4Hz, aka 250ms */
 | |
| +	*delay_on = 250 / 2;
 | |
| +	*delay_off = 250 / 2;
 | |
| +
 | |
| +	return 0;
 | |
| +}
 | |
| +
 | |
| +static int qca808x_led_polarity_set(struct phy_device *phydev, int index,
 | |
| +				    unsigned long modes)
 | |
| +{
 | |
| +	struct at803x_priv *priv = phydev->priv;
 | |
| +	bool active_low = false;
 | |
| +	u32 mode;
 | |
| +
 | |
| +	for_each_set_bit(mode, &modes, __PHY_LED_MODES_NUM) {
 | |
| +		switch (mode) {
 | |
| +		case PHY_LED_ACTIVE_LOW:
 | |
| +			active_low = true;
 | |
| +			break;
 | |
| +		default:
 | |
| +			return -EINVAL;
 | |
| +		}
 | |
| +	}
 | |
| +
 | |
| +	/* PHY polarity is global and can't be set per LED.
 | |
| +	 * To detect this, check if last requested polarity mode
 | |
| +	 * match the new one.
 | |
| +	 */
 | |
| +	if (priv->led_polarity_mode >= 0 &&
 | |
| +	    priv->led_polarity_mode != active_low) {
 | |
| +		phydev_err(phydev, "PHY polarity is global. Mismatched polarity on different LED\n");
 | |
| +		return -EINVAL;
 | |
| +	}
 | |
| +
 | |
| +	/* Save the last PHY polarity mode */
 | |
| +	priv->led_polarity_mode = active_low;
 | |
| +
 | |
| +	return phy_modify_mmd(phydev, MDIO_MMD_AN,
 | |
| +			      QCA808X_MMD7_LED_POLARITY_CTRL,
 | |
| +			      QCA808X_LED_ACTIVE_HIGH,
 | |
| +			      active_low ? 0 : QCA808X_LED_ACTIVE_HIGH);
 | |
| +}
 | |
| +
 | |
|  static struct phy_driver at803x_driver[] = {
 | |
|  {
 | |
|  	/* Qualcomm Atheros AR8035 */
 | |
| @@ -2411,6 +2732,12 @@ static struct phy_driver at803x_driver[]
 | |
|  	.cable_test_start	= qca808x_cable_test_start,
 | |
|  	.cable_test_get_status	= qca808x_cable_test_get_status,
 | |
|  	.link_change_notify	= qca808x_link_change_notify,
 | |
| +	.led_brightness_set	= qca808x_led_brightness_set,
 | |
| +	.led_blink_set		= qca808x_led_blink_set,
 | |
| +	.led_hw_is_supported	= qca808x_led_hw_is_supported,
 | |
| +	.led_hw_control_set	= qca808x_led_hw_control_set,
 | |
| +	.led_hw_control_get	= qca808x_led_hw_control_get,
 | |
| +	.led_polarity_set	= qca808x_led_polarity_set,
 | |
|  }, };
 | |
|  
 | |
|  module_phy_driver(at803x_driver);
 |