677 lines · c
1// SPDX-License-Identifier: GPL-2.02 3#include <linux/phy.h>4#include <linux/module.h>5 6#include <linux/netdevice.h>7#include <linux/etherdevice.h>8#include <linux/ethtool_netlink.h>9 10#include "qcom.h"11 12MODULE_DESCRIPTION("Qualcomm PHY driver Common Functions");13MODULE_AUTHOR("Matus Ujhelyi");14MODULE_AUTHOR("Christian Marangi <ansuelsmth@gmail.com>");15MODULE_LICENSE("GPL");16 17int at803x_debug_reg_read(struct phy_device *phydev, u16 reg)18{19 int ret;20 21 ret = phy_write(phydev, AT803X_DEBUG_ADDR, reg);22 if (ret < 0)23 return ret;24 25 return phy_read(phydev, AT803X_DEBUG_DATA);26}27EXPORT_SYMBOL_GPL(at803x_debug_reg_read);28 29int at803x_debug_reg_mask(struct phy_device *phydev, u16 reg,30 u16 clear, u16 set)31{32 u16 val;33 int ret;34 35 ret = at803x_debug_reg_read(phydev, reg);36 if (ret < 0)37 return ret;38 39 val = ret & 0xffff;40 val &= ~clear;41 val |= set;42 43 return phy_write(phydev, AT803X_DEBUG_DATA, val);44}45EXPORT_SYMBOL_GPL(at803x_debug_reg_mask);46 47int at803x_debug_reg_write(struct phy_device *phydev, u16 reg, u16 data)48{49 int ret;50 51 ret = phy_write(phydev, AT803X_DEBUG_ADDR, reg);52 if (ret < 0)53 return ret;54 55 return phy_write(phydev, AT803X_DEBUG_DATA, data);56}57EXPORT_SYMBOL_GPL(at803x_debug_reg_write);58 59int at803x_set_wol(struct phy_device *phydev,60 struct ethtool_wolinfo *wol)61{62 int ret, irq_enabled;63 64 if (wol->wolopts & WAKE_MAGIC) {65 struct net_device *ndev = phydev->attached_dev;66 const u8 *mac;67 unsigned int i;68 static const unsigned int offsets[] = {69 AT803X_LOC_MAC_ADDR_32_47_OFFSET,70 AT803X_LOC_MAC_ADDR_16_31_OFFSET,71 AT803X_LOC_MAC_ADDR_0_15_OFFSET,72 };73 74 if (!ndev)75 return -ENODEV;76 77 mac = (const u8 *)ndev->dev_addr;78 79 if (!is_valid_ether_addr(mac))80 return -EINVAL;81 82 for (i = 0; i < 3; i++)83 phy_write_mmd(phydev, MDIO_MMD_PCS, offsets[i],84 mac[(i * 2) + 1] | (mac[(i * 2)] << 8));85 86 /* Enable WOL interrupt */87 ret = phy_modify(phydev, AT803X_INTR_ENABLE, 0, AT803X_INTR_ENABLE_WOL);88 if (ret)89 return ret;90 } else {91 /* Disable WOL interrupt */92 ret = phy_modify(phydev, AT803X_INTR_ENABLE, AT803X_INTR_ENABLE_WOL, 0);93 if (ret)94 return ret;95 }96 97 /* Clear WOL status */98 ret = phy_read(phydev, AT803X_INTR_STATUS);99 if (ret < 0)100 return ret;101 102 /* Check if there are other interrupts except for WOL triggered when PHY is103 * in interrupt mode, only the interrupts enabled by AT803X_INTR_ENABLE can104 * be passed up to the interrupt PIN.105 */106 irq_enabled = phy_read(phydev, AT803X_INTR_ENABLE);107 if (irq_enabled < 0)108 return irq_enabled;109 110 irq_enabled &= ~AT803X_INTR_ENABLE_WOL;111 if (ret & irq_enabled && !phy_polling_mode(phydev))112 phy_trigger_machine(phydev);113 114 return 0;115}116EXPORT_SYMBOL_GPL(at803x_set_wol);117 118void at803x_get_wol(struct phy_device *phydev,119 struct ethtool_wolinfo *wol)120{121 int value;122 123 wol->supported = WAKE_MAGIC;124 wol->wolopts = 0;125 126 value = phy_read(phydev, AT803X_INTR_ENABLE);127 if (value < 0)128 return;129 130 if (value & AT803X_INTR_ENABLE_WOL)131 wol->wolopts |= WAKE_MAGIC;132}133EXPORT_SYMBOL_GPL(at803x_get_wol);134 135int at803x_ack_interrupt(struct phy_device *phydev)136{137 int err;138 139 err = phy_read(phydev, AT803X_INTR_STATUS);140 141 return (err < 0) ? err : 0;142}143EXPORT_SYMBOL_GPL(at803x_ack_interrupt);144 145int at803x_config_intr(struct phy_device *phydev)146{147 int err;148 int value;149 150 value = phy_read(phydev, AT803X_INTR_ENABLE);151 152 if (phydev->interrupts == PHY_INTERRUPT_ENABLED) {153 /* Clear any pending interrupts */154 err = at803x_ack_interrupt(phydev);155 if (err)156 return err;157 158 value |= AT803X_INTR_ENABLE_AUTONEG_ERR;159 value |= AT803X_INTR_ENABLE_SPEED_CHANGED;160 value |= AT803X_INTR_ENABLE_DUPLEX_CHANGED;161 value |= AT803X_INTR_ENABLE_LINK_FAIL;162 value |= AT803X_INTR_ENABLE_LINK_SUCCESS;163 164 err = phy_write(phydev, AT803X_INTR_ENABLE, value);165 } else {166 err = phy_write(phydev, AT803X_INTR_ENABLE, 0);167 if (err)168 return err;169 170 /* Clear any pending interrupts */171 err = at803x_ack_interrupt(phydev);172 }173 174 return err;175}176EXPORT_SYMBOL_GPL(at803x_config_intr);177 178irqreturn_t at803x_handle_interrupt(struct phy_device *phydev)179{180 int irq_status, int_enabled;181 182 irq_status = phy_read(phydev, AT803X_INTR_STATUS);183 if (irq_status < 0) {184 phy_error(phydev);185 return IRQ_NONE;186 }187 188 /* Read the current enabled interrupts */189 int_enabled = phy_read(phydev, AT803X_INTR_ENABLE);190 if (int_enabled < 0) {191 phy_error(phydev);192 return IRQ_NONE;193 }194 195 /* See if this was one of our enabled interrupts */196 if (!(irq_status & int_enabled))197 return IRQ_NONE;198 199 phy_trigger_machine(phydev);200 201 return IRQ_HANDLED;202}203EXPORT_SYMBOL_GPL(at803x_handle_interrupt);204 205int at803x_read_specific_status(struct phy_device *phydev,206 struct at803x_ss_mask ss_mask)207{208 int ss;209 210 /* Read the AT8035 PHY-Specific Status register, which indicates the211 * speed and duplex that the PHY is actually using, irrespective of212 * whether we are in autoneg mode or not.213 */214 ss = phy_read(phydev, AT803X_SPECIFIC_STATUS);215 if (ss < 0)216 return ss;217 218 if (ss & AT803X_SS_SPEED_DUPLEX_RESOLVED) {219 int sfc, speed;220 221 sfc = phy_read(phydev, AT803X_SPECIFIC_FUNCTION_CONTROL);222 if (sfc < 0)223 return sfc;224 225 speed = ss & ss_mask.speed_mask;226 speed >>= ss_mask.speed_shift;227 228 switch (speed) {229 case AT803X_SS_SPEED_10:230 phydev->speed = SPEED_10;231 break;232 case AT803X_SS_SPEED_100:233 phydev->speed = SPEED_100;234 break;235 case AT803X_SS_SPEED_1000:236 phydev->speed = SPEED_1000;237 break;238 case QCA808X_SS_SPEED_2500:239 phydev->speed = SPEED_2500;240 break;241 }242 if (ss & AT803X_SS_DUPLEX)243 phydev->duplex = DUPLEX_FULL;244 else245 phydev->duplex = DUPLEX_HALF;246 247 if (ss & AT803X_SS_MDIX)248 phydev->mdix = ETH_TP_MDI_X;249 else250 phydev->mdix = ETH_TP_MDI;251 252 switch (FIELD_GET(AT803X_SFC_MDI_CROSSOVER_MODE_M, sfc)) {253 case AT803X_SFC_MANUAL_MDI:254 phydev->mdix_ctrl = ETH_TP_MDI;255 break;256 case AT803X_SFC_MANUAL_MDIX:257 phydev->mdix_ctrl = ETH_TP_MDI_X;258 break;259 case AT803X_SFC_AUTOMATIC_CROSSOVER:260 phydev->mdix_ctrl = ETH_TP_MDI_AUTO;261 break;262 }263 }264 265 return 0;266}267EXPORT_SYMBOL_GPL(at803x_read_specific_status);268 269int at803x_config_mdix(struct phy_device *phydev, u8 ctrl)270{271 u16 val;272 273 switch (ctrl) {274 case ETH_TP_MDI:275 val = AT803X_SFC_MANUAL_MDI;276 break;277 case ETH_TP_MDI_X:278 val = AT803X_SFC_MANUAL_MDIX;279 break;280 case ETH_TP_MDI_AUTO:281 val = AT803X_SFC_AUTOMATIC_CROSSOVER;282 break;283 default:284 return 0;285 }286 287 return phy_modify_changed(phydev, AT803X_SPECIFIC_FUNCTION_CONTROL,288 AT803X_SFC_MDI_CROSSOVER_MODE_M,289 FIELD_PREP(AT803X_SFC_MDI_CROSSOVER_MODE_M, val));290}291EXPORT_SYMBOL_GPL(at803x_config_mdix);292 293int at803x_prepare_config_aneg(struct phy_device *phydev)294{295 int ret;296 297 ret = at803x_config_mdix(phydev, phydev->mdix_ctrl);298 if (ret < 0)299 return ret;300 301 /* Changes of the midx bits are disruptive to the normal operation;302 * therefore any changes to these registers must be followed by a303 * software reset to take effect.304 */305 if (ret == 1) {306 ret = genphy_soft_reset(phydev);307 if (ret < 0)308 return ret;309 }310 311 return 0;312}313EXPORT_SYMBOL_GPL(at803x_prepare_config_aneg);314 315int at803x_read_status(struct phy_device *phydev)316{317 struct at803x_ss_mask ss_mask = { 0 };318 int err, old_link = phydev->link;319 320 /* Update the link, but return if there was an error */321 err = genphy_update_link(phydev);322 if (err)323 return err;324 325 /* why bother the PHY if nothing can have changed */326 if (phydev->autoneg == AUTONEG_ENABLE && old_link && phydev->link)327 return 0;328 329 phydev->speed = SPEED_UNKNOWN;330 phydev->duplex = DUPLEX_UNKNOWN;331 phydev->pause = 0;332 phydev->asym_pause = 0;333 334 err = genphy_read_lpa(phydev);335 if (err < 0)336 return err;337 338 ss_mask.speed_mask = AT803X_SS_SPEED_MASK;339 ss_mask.speed_shift = __bf_shf(AT803X_SS_SPEED_MASK);340 err = at803x_read_specific_status(phydev, ss_mask);341 if (err < 0)342 return err;343 344 if (phydev->autoneg == AUTONEG_ENABLE && phydev->autoneg_complete)345 phy_resolve_aneg_pause(phydev);346 347 return 0;348}349EXPORT_SYMBOL_GPL(at803x_read_status);350 351static int at803x_get_downshift(struct phy_device *phydev, u8 *d)352{353 int val;354 355 val = phy_read(phydev, AT803X_SMART_SPEED);356 if (val < 0)357 return val;358 359 if (val & AT803X_SMART_SPEED_ENABLE)360 *d = FIELD_GET(AT803X_SMART_SPEED_RETRY_LIMIT_MASK, val) + 2;361 else362 *d = DOWNSHIFT_DEV_DISABLE;363 364 return 0;365}366 367static int at803x_set_downshift(struct phy_device *phydev, u8 cnt)368{369 u16 mask, set;370 int ret;371 372 switch (cnt) {373 case DOWNSHIFT_DEV_DEFAULT_COUNT:374 cnt = AT803X_DEFAULT_DOWNSHIFT;375 fallthrough;376 case AT803X_MIN_DOWNSHIFT ... AT803X_MAX_DOWNSHIFT:377 set = AT803X_SMART_SPEED_ENABLE |378 AT803X_SMART_SPEED_BYPASS_TIMER |379 FIELD_PREP(AT803X_SMART_SPEED_RETRY_LIMIT_MASK, cnt - 2);380 mask = AT803X_SMART_SPEED_RETRY_LIMIT_MASK;381 break;382 case DOWNSHIFT_DEV_DISABLE:383 set = 0;384 mask = AT803X_SMART_SPEED_ENABLE |385 AT803X_SMART_SPEED_BYPASS_TIMER;386 break;387 default:388 return -EINVAL;389 }390 391 ret = phy_modify_changed(phydev, AT803X_SMART_SPEED, mask, set);392 393 /* After changing the smart speed settings, we need to perform a394 * software reset, use phy_init_hw() to make sure we set the395 * reapply any values which might got lost during software reset.396 */397 if (ret == 1)398 ret = phy_init_hw(phydev);399 400 return ret;401}402 403int at803x_get_tunable(struct phy_device *phydev,404 struct ethtool_tunable *tuna, void *data)405{406 switch (tuna->id) {407 case ETHTOOL_PHY_DOWNSHIFT:408 return at803x_get_downshift(phydev, data);409 default:410 return -EOPNOTSUPP;411 }412}413EXPORT_SYMBOL_GPL(at803x_get_tunable);414 415int at803x_set_tunable(struct phy_device *phydev,416 struct ethtool_tunable *tuna, const void *data)417{418 switch (tuna->id) {419 case ETHTOOL_PHY_DOWNSHIFT:420 return at803x_set_downshift(phydev, *(const u8 *)data);421 default:422 return -EOPNOTSUPP;423 }424}425EXPORT_SYMBOL_GPL(at803x_set_tunable);426 427int at803x_cdt_fault_length(int dt)428{429 /* According to the datasheet the distance to the fault is430 * DELTA_TIME * 0.824 meters.431 *432 * The author suspect the correct formula is:433 *434 * fault_distance = DELTA_TIME * (c * VF) / 125MHz / 2435 *436 * where c is the speed of light, VF is the velocity factor of437 * the twisted pair cable, 125MHz the counter frequency and438 * we need to divide by 2 because the hardware will measure the439 * round trip time to the fault and back to the PHY.440 *441 * With a VF of 0.69 we get the factor 0.824 mentioned in the442 * datasheet.443 */444 return (dt * 824) / 10;445}446EXPORT_SYMBOL_GPL(at803x_cdt_fault_length);447 448int at803x_cdt_start(struct phy_device *phydev, u32 cdt_start)449{450 return phy_write(phydev, AT803X_CDT, cdt_start);451}452EXPORT_SYMBOL_GPL(at803x_cdt_start);453 454int at803x_cdt_wait_for_completion(struct phy_device *phydev,455 u32 cdt_en)456{457 int val, ret;458 459 /* One test run takes about 25ms */460 ret = phy_read_poll_timeout(phydev, AT803X_CDT, val,461 !(val & cdt_en),462 30000, 100000, true);463 464 return ret < 0 ? ret : 0;465}466EXPORT_SYMBOL_GPL(at803x_cdt_wait_for_completion);467 468static bool qca808x_cdt_fault_length_valid(int cdt_code)469{470 switch (cdt_code) {471 case QCA808X_CDT_STATUS_STAT_SAME_SHORT:472 case QCA808X_CDT_STATUS_STAT_SAME_OPEN:473 case QCA808X_CDT_STATUS_STAT_CROSS_SHORT_WITH_MDI1_SAME_NORMAL:474 case QCA808X_CDT_STATUS_STAT_CROSS_SHORT_WITH_MDI1_SAME_OPEN:475 case QCA808X_CDT_STATUS_STAT_CROSS_SHORT_WITH_MDI1_SAME_SHORT:476 case QCA808X_CDT_STATUS_STAT_CROSS_SHORT_WITH_MDI2_SAME_NORMAL:477 case QCA808X_CDT_STATUS_STAT_CROSS_SHORT_WITH_MDI2_SAME_OPEN:478 case QCA808X_CDT_STATUS_STAT_CROSS_SHORT_WITH_MDI2_SAME_SHORT:479 case QCA808X_CDT_STATUS_STAT_CROSS_SHORT_WITH_MDI3_SAME_NORMAL:480 case QCA808X_CDT_STATUS_STAT_CROSS_SHORT_WITH_MDI3_SAME_OPEN:481 case QCA808X_CDT_STATUS_STAT_CROSS_SHORT_WITH_MDI3_SAME_SHORT:482 return true;483 default:484 return false;485 }486}487 488static int qca808x_cable_test_result_trans(int cdt_code)489{490 switch (cdt_code) {491 case QCA808X_CDT_STATUS_STAT_NORMAL:492 return ETHTOOL_A_CABLE_RESULT_CODE_OK;493 case QCA808X_CDT_STATUS_STAT_SAME_SHORT:494 return ETHTOOL_A_CABLE_RESULT_CODE_SAME_SHORT;495 case QCA808X_CDT_STATUS_STAT_SAME_OPEN:496 return ETHTOOL_A_CABLE_RESULT_CODE_OPEN;497 case QCA808X_CDT_STATUS_STAT_CROSS_SHORT_WITH_MDI1_SAME_NORMAL:498 case QCA808X_CDT_STATUS_STAT_CROSS_SHORT_WITH_MDI1_SAME_OPEN:499 case QCA808X_CDT_STATUS_STAT_CROSS_SHORT_WITH_MDI1_SAME_SHORT:500 case QCA808X_CDT_STATUS_STAT_CROSS_SHORT_WITH_MDI2_SAME_NORMAL:501 case QCA808X_CDT_STATUS_STAT_CROSS_SHORT_WITH_MDI2_SAME_OPEN:502 case QCA808X_CDT_STATUS_STAT_CROSS_SHORT_WITH_MDI2_SAME_SHORT:503 case QCA808X_CDT_STATUS_STAT_CROSS_SHORT_WITH_MDI3_SAME_NORMAL:504 case QCA808X_CDT_STATUS_STAT_CROSS_SHORT_WITH_MDI3_SAME_OPEN:505 case QCA808X_CDT_STATUS_STAT_CROSS_SHORT_WITH_MDI3_SAME_SHORT:506 return ETHTOOL_A_CABLE_RESULT_CODE_CROSS_SHORT;507 case QCA808X_CDT_STATUS_STAT_FAIL:508 default:509 return ETHTOOL_A_CABLE_RESULT_CODE_UNSPEC;510 }511}512 513static int qca808x_cdt_fault_length(struct phy_device *phydev, int pair,514 int result)515{516 int val;517 u32 cdt_length_reg = 0;518 519 switch (pair) {520 case ETHTOOL_A_CABLE_PAIR_A:521 cdt_length_reg = QCA808X_MMD3_CDT_DIAG_PAIR_A;522 break;523 case ETHTOOL_A_CABLE_PAIR_B:524 cdt_length_reg = QCA808X_MMD3_CDT_DIAG_PAIR_B;525 break;526 case ETHTOOL_A_CABLE_PAIR_C:527 cdt_length_reg = QCA808X_MMD3_CDT_DIAG_PAIR_C;528 break;529 case ETHTOOL_A_CABLE_PAIR_D:530 cdt_length_reg = QCA808X_MMD3_CDT_DIAG_PAIR_D;531 break;532 default:533 return -EINVAL;534 }535 536 val = phy_read_mmd(phydev, MDIO_MMD_PCS, cdt_length_reg);537 if (val < 0)538 return val;539 540 if (result == ETHTOOL_A_CABLE_RESULT_CODE_SAME_SHORT)541 val = FIELD_GET(QCA808X_CDT_DIAG_LENGTH_SAME_SHORT, val);542 else543 val = FIELD_GET(QCA808X_CDT_DIAG_LENGTH_CROSS_SHORT, val);544 545 return at803x_cdt_fault_length(val);546}547 548static int qca808x_cable_test_get_pair_status(struct phy_device *phydev, u8 pair,549 u16 status)550{551 int length, result;552 u16 pair_code;553 554 switch (pair) {555 case ETHTOOL_A_CABLE_PAIR_A:556 pair_code = FIELD_GET(QCA808X_CDT_CODE_PAIR_A, status);557 break;558 case ETHTOOL_A_CABLE_PAIR_B:559 pair_code = FIELD_GET(QCA808X_CDT_CODE_PAIR_B, status);560 break;561 case ETHTOOL_A_CABLE_PAIR_C:562 pair_code = FIELD_GET(QCA808X_CDT_CODE_PAIR_C, status);563 break;564 case ETHTOOL_A_CABLE_PAIR_D:565 pair_code = FIELD_GET(QCA808X_CDT_CODE_PAIR_D, status);566 break;567 default:568 return -EINVAL;569 }570 571 result = qca808x_cable_test_result_trans(pair_code);572 ethnl_cable_test_result(phydev, pair, result);573 574 if (qca808x_cdt_fault_length_valid(pair_code)) {575 length = qca808x_cdt_fault_length(phydev, pair, result);576 ethnl_cable_test_fault_length(phydev, pair, length);577 }578 579 return 0;580}581 582int qca808x_cable_test_get_status(struct phy_device *phydev, bool *finished)583{584 int ret, val;585 586 *finished = false;587 588 val = QCA808X_CDT_ENABLE_TEST |589 QCA808X_CDT_LENGTH_UNIT;590 ret = at803x_cdt_start(phydev, val);591 if (ret)592 return ret;593 594 ret = at803x_cdt_wait_for_completion(phydev, QCA808X_CDT_ENABLE_TEST);595 if (ret)596 return ret;597 598 val = phy_read_mmd(phydev, MDIO_MMD_PCS, QCA808X_MMD3_CDT_STATUS);599 if (val < 0)600 return val;601 602 ret = qca808x_cable_test_get_pair_status(phydev, ETHTOOL_A_CABLE_PAIR_A, val);603 if (ret)604 return ret;605 606 ret = qca808x_cable_test_get_pair_status(phydev, ETHTOOL_A_CABLE_PAIR_B, val);607 if (ret)608 return ret;609 610 ret = qca808x_cable_test_get_pair_status(phydev, ETHTOOL_A_CABLE_PAIR_C, val);611 if (ret)612 return ret;613 614 ret = qca808x_cable_test_get_pair_status(phydev, ETHTOOL_A_CABLE_PAIR_D, val);615 if (ret)616 return ret;617 618 *finished = true;619 620 return 0;621}622EXPORT_SYMBOL_GPL(qca808x_cable_test_get_status);623 624int qca808x_led_reg_hw_control_enable(struct phy_device *phydev, u16 reg)625{626 return phy_clear_bits_mmd(phydev, MDIO_MMD_AN, reg,627 QCA808X_LED_FORCE_EN);628}629EXPORT_SYMBOL_GPL(qca808x_led_reg_hw_control_enable);630 631bool qca808x_led_reg_hw_control_status(struct phy_device *phydev, u16 reg)632{633 int val;634 635 val = phy_read_mmd(phydev, MDIO_MMD_AN, reg);636 return !(val & QCA808X_LED_FORCE_EN);637}638EXPORT_SYMBOL_GPL(qca808x_led_reg_hw_control_status);639 640int qca808x_led_reg_brightness_set(struct phy_device *phydev,641 u16 reg, enum led_brightness value)642{643 return phy_modify_mmd(phydev, MDIO_MMD_AN, reg,644 QCA808X_LED_FORCE_EN | QCA808X_LED_FORCE_MODE_MASK,645 QCA808X_LED_FORCE_EN | (value ? QCA808X_LED_FORCE_ON :646 QCA808X_LED_FORCE_OFF));647}648EXPORT_SYMBOL_GPL(qca808x_led_reg_brightness_set);649 650int qca808x_led_reg_blink_set(struct phy_device *phydev, u16 reg,651 unsigned long *delay_on,652 unsigned long *delay_off)653{654 int ret;655 656 /* Set blink to 50% off, 50% on at 4Hz by default */657 ret = phy_modify_mmd(phydev, MDIO_MMD_AN, QCA808X_MMD7_LED_GLOBAL,658 QCA808X_LED_BLINK_FREQ_MASK | QCA808X_LED_BLINK_DUTY_MASK,659 QCA808X_LED_BLINK_FREQ_4HZ | QCA808X_LED_BLINK_DUTY_50_50);660 if (ret)661 return ret;662 663 /* We use BLINK_1 for normal blinking */664 ret = phy_modify_mmd(phydev, MDIO_MMD_AN, reg,665 QCA808X_LED_FORCE_EN | QCA808X_LED_FORCE_MODE_MASK,666 QCA808X_LED_FORCE_EN | QCA808X_LED_FORCE_BLINK_1);667 if (ret)668 return ret;669 670 /* We set blink to 4Hz, aka 250ms */671 *delay_on = 250 / 2;672 *delay_off = 250 / 2;673 674 return 0;675}676EXPORT_SYMBOL_GPL(qca808x_led_reg_blink_set);677