| // SPDX-License-Identifier: GPL-2.0+ |
| |
| #include "lan966x_main.h" |
| |
| #define LAN966X_TAPRIO_TIMEOUT_MS 1000 |
| #define LAN966X_TAPRIO_ENTRIES_PER_PORT 2 |
| |
| /* Minimum supported cycle time in nanoseconds */ |
| #define LAN966X_TAPRIO_MIN_CYCLE_TIME_NS NSEC_PER_USEC |
| |
| /* Maximum supported cycle time in nanoseconds */ |
| #define LAN966X_TAPRIO_MAX_CYCLE_TIME_NS (NSEC_PER_SEC - 1) |
| |
| /* Total number of TAS GCL entries */ |
| #define LAN966X_TAPRIO_NUM_GCL 256 |
| |
| /* TAPRIO link speeds for calculation of guard band */ |
| enum lan966x_taprio_link_speed { |
| LAN966X_TAPRIO_SPEED_NO_GB, |
| LAN966X_TAPRIO_SPEED_10, |
| LAN966X_TAPRIO_SPEED_100, |
| LAN966X_TAPRIO_SPEED_1000, |
| LAN966X_TAPRIO_SPEED_2500, |
| }; |
| |
| /* TAPRIO list states */ |
| enum lan966x_taprio_state { |
| LAN966X_TAPRIO_STATE_ADMIN, |
| LAN966X_TAPRIO_STATE_ADVANCING, |
| LAN966X_TAPRIO_STATE_PENDING, |
| LAN966X_TAPRIO_STATE_OPERATING, |
| LAN966X_TAPRIO_STATE_TERMINATING, |
| LAN966X_TAPRIO_STATE_MAX, |
| }; |
| |
| /* TAPRIO GCL command */ |
| enum lan966x_taprio_gcl_cmd { |
| LAN966X_TAPRIO_GCL_CMD_SET_GATE_STATES = 0, |
| }; |
| |
| static u32 lan966x_taprio_list_index(struct lan966x_port *port, u8 entry) |
| { |
| return port->chip_port * LAN966X_TAPRIO_ENTRIES_PER_PORT + entry; |
| } |
| |
| static u32 lan966x_taprio_list_state_get(struct lan966x_port *port) |
| { |
| struct lan966x *lan966x = port->lan966x; |
| u32 val; |
| |
| val = lan_rd(lan966x, QSYS_TAS_LST); |
| return QSYS_TAS_LST_LIST_STATE_GET(val); |
| } |
| |
| static u32 lan966x_taprio_list_index_state_get(struct lan966x_port *port, |
| u32 list) |
| { |
| struct lan966x *lan966x = port->lan966x; |
| |
| lan_rmw(QSYS_TAS_CFG_CTRL_LIST_NUM_SET(list), |
| QSYS_TAS_CFG_CTRL_LIST_NUM, |
| lan966x, QSYS_TAS_CFG_CTRL); |
| |
| return lan966x_taprio_list_state_get(port); |
| } |
| |
| static void lan966x_taprio_list_state_set(struct lan966x_port *port, |
| u32 state) |
| { |
| struct lan966x *lan966x = port->lan966x; |
| |
| lan_rmw(QSYS_TAS_LST_LIST_STATE_SET(state), |
| QSYS_TAS_LST_LIST_STATE, |
| lan966x, QSYS_TAS_LST); |
| } |
| |
| static int lan966x_taprio_list_shutdown(struct lan966x_port *port, |
| u32 list) |
| { |
| struct lan966x *lan966x = port->lan966x; |
| bool pending, operating; |
| unsigned long end; |
| u32 state; |
| |
| end = jiffies + msecs_to_jiffies(LAN966X_TAPRIO_TIMEOUT_MS); |
| /* It is required to try multiple times to set the state of list, |
| * because the HW can overwrite this. |
| */ |
| do { |
| state = lan966x_taprio_list_state_get(port); |
| |
| pending = false; |
| operating = false; |
| |
| if (state == LAN966X_TAPRIO_STATE_ADVANCING || |
| state == LAN966X_TAPRIO_STATE_PENDING) { |
| lan966x_taprio_list_state_set(port, |
| LAN966X_TAPRIO_STATE_ADMIN); |
| pending = true; |
| } |
| |
| if (state == LAN966X_TAPRIO_STATE_OPERATING) { |
| lan966x_taprio_list_state_set(port, |
| LAN966X_TAPRIO_STATE_TERMINATING); |
| operating = true; |
| } |
| |
| /* If the entry was in pending and now gets in admin, then there |
| * is nothing else to do, so just bail out |
| */ |
| state = lan966x_taprio_list_state_get(port); |
| if (pending && |
| state == LAN966X_TAPRIO_STATE_ADMIN) |
| return 0; |
| |
| /* If the list was in operating and now is in terminating or |
| * admin, then is OK to exit but it needs to wait until the list |
| * will get in admin. It is not required to set the state |
| * again. |
| */ |
| if (operating && |
| (state == LAN966X_TAPRIO_STATE_TERMINATING || |
| state == LAN966X_TAPRIO_STATE_ADMIN)) |
| break; |
| |
| } while (!time_after(jiffies, end)); |
| |
| end = jiffies + msecs_to_jiffies(LAN966X_TAPRIO_TIMEOUT_MS); |
| do { |
| state = lan966x_taprio_list_state_get(port); |
| if (state == LAN966X_TAPRIO_STATE_ADMIN) |
| break; |
| |
| } while (!time_after(jiffies, end)); |
| |
| /* If the list was in operating mode, it could be stopped while some |
| * queues where closed, so make sure to restore "all-queues-open" |
| */ |
| if (operating) { |
| lan_wr(QSYS_TAS_GS_CTRL_HSCH_POS_SET(port->chip_port), |
| lan966x, QSYS_TAS_GS_CTRL); |
| |
| lan_wr(QSYS_TAS_GATE_STATE_TAS_GATE_STATE_SET(0xff), |
| lan966x, QSYS_TAS_GATE_STATE); |
| } |
| |
| return 0; |
| } |
| |
| static int lan966x_taprio_shutdown(struct lan966x_port *port) |
| { |
| u32 i, list, state; |
| int err; |
| |
| for (i = 0; i < LAN966X_TAPRIO_ENTRIES_PER_PORT; ++i) { |
| list = lan966x_taprio_list_index(port, i); |
| state = lan966x_taprio_list_index_state_get(port, list); |
| if (state == LAN966X_TAPRIO_STATE_ADMIN) |
| continue; |
| |
| err = lan966x_taprio_list_shutdown(port, list); |
| if (err) |
| return err; |
| } |
| |
| return 0; |
| } |
| |
| /* Find a suitable list for a new schedule. First priority is a list in state |
| * pending. Second priority is a list in state admin. |
| */ |
| static int lan966x_taprio_find_list(struct lan966x_port *port, |
| struct tc_taprio_qopt_offload *qopt, |
| int *new_list, int *obs_list) |
| { |
| int state[LAN966X_TAPRIO_ENTRIES_PER_PORT]; |
| int list[LAN966X_TAPRIO_ENTRIES_PER_PORT]; |
| int err, oper = -1; |
| u32 i; |
| |
| *new_list = -1; |
| *obs_list = -1; |
| |
| /* If there is already an entry in operating mode, return this list in |
| * obs_list, such that when the new list will get activated the |
| * operating list will be stopped. In this way is possible to have |
| * smooth transitions between the lists |
| */ |
| for (i = 0; i < LAN966X_TAPRIO_ENTRIES_PER_PORT; ++i) { |
| list[i] = lan966x_taprio_list_index(port, i); |
| state[i] = lan966x_taprio_list_index_state_get(port, list[i]); |
| if (state[i] == LAN966X_TAPRIO_STATE_OPERATING) |
| oper = list[i]; |
| } |
| |
| for (i = 0; i < LAN966X_TAPRIO_ENTRIES_PER_PORT; ++i) { |
| if (state[i] == LAN966X_TAPRIO_STATE_PENDING) { |
| err = lan966x_taprio_shutdown(port); |
| if (err) |
| return err; |
| |
| *new_list = list[i]; |
| *obs_list = (oper == -1) ? *new_list : oper; |
| return 0; |
| } |
| } |
| |
| for (i = 0; i < LAN966X_TAPRIO_ENTRIES_PER_PORT; ++i) { |
| if (state[i] == LAN966X_TAPRIO_STATE_ADMIN) { |
| *new_list = list[i]; |
| *obs_list = (oper == -1) ? *new_list : oper; |
| return 0; |
| } |
| } |
| |
| return -ENOSPC; |
| } |
| |
| static int lan966x_taprio_check(struct tc_taprio_qopt_offload *qopt) |
| { |
| u64 total_time = 0; |
| u32 i; |
| |
| /* This is not supported by th HW */ |
| if (qopt->cycle_time_extension) |
| return -EOPNOTSUPP; |
| |
| /* There is a limited number of gcl entries that can be used, they are |
| * shared by all ports |
| */ |
| if (qopt->num_entries > LAN966X_TAPRIO_NUM_GCL) |
| return -EINVAL; |
| |
| /* Don't allow cycle times bigger than 1 sec or smaller than 1 usec */ |
| if (qopt->cycle_time < LAN966X_TAPRIO_MIN_CYCLE_TIME_NS || |
| qopt->cycle_time > LAN966X_TAPRIO_MAX_CYCLE_TIME_NS) |
| return -EINVAL; |
| |
| for (i = 0; i < qopt->num_entries; ++i) { |
| struct tc_taprio_sched_entry *entry = &qopt->entries[i]; |
| |
| /* Don't allow intervals bigger than 1 sec or smaller than 1 |
| * usec |
| */ |
| if (entry->interval < LAN966X_TAPRIO_MIN_CYCLE_TIME_NS || |
| entry->interval > LAN966X_TAPRIO_MAX_CYCLE_TIME_NS) |
| return -EINVAL; |
| |
| if (qopt->entries[i].command != TC_TAPRIO_CMD_SET_GATES) |
| return -EINVAL; |
| |
| total_time += qopt->entries[i].interval; |
| } |
| |
| /* Don't allow the total time of intervals be bigger than 1 sec */ |
| if (total_time > LAN966X_TAPRIO_MAX_CYCLE_TIME_NS) |
| return -EINVAL; |
| |
| /* The HW expects that the cycle time to be at least as big as sum of |
| * each interval of gcl |
| */ |
| if (qopt->cycle_time < total_time) |
| return -EINVAL; |
| |
| return 0; |
| } |
| |
| static int lan966x_taprio_gcl_free_get(struct lan966x_port *port, |
| unsigned long *free_list) |
| { |
| struct lan966x *lan966x = port->lan966x; |
| u32 num_free, state, list; |
| u32 base, next, max_list; |
| |
| /* By default everything is free */ |
| bitmap_fill(free_list, LAN966X_TAPRIO_NUM_GCL); |
| num_free = LAN966X_TAPRIO_NUM_GCL; |
| |
| /* Iterate over all gcl entries and find out which are free. And mark |
| * those that are not free. |
| */ |
| max_list = lan966x->num_phys_ports * LAN966X_TAPRIO_ENTRIES_PER_PORT; |
| for (list = 0; list < max_list; ++list) { |
| state = lan966x_taprio_list_index_state_get(port, list); |
| if (state == LAN966X_TAPRIO_STATE_ADMIN) |
| continue; |
| |
| base = lan_rd(lan966x, QSYS_TAS_LIST_CFG); |
| base = QSYS_TAS_LIST_CFG_LIST_BASE_ADDR_GET(base); |
| next = base; |
| |
| do { |
| clear_bit(next, free_list); |
| num_free--; |
| |
| lan_rmw(QSYS_TAS_CFG_CTRL_GCL_ENTRY_NUM_SET(next), |
| QSYS_TAS_CFG_CTRL_GCL_ENTRY_NUM, |
| lan966x, QSYS_TAS_CFG_CTRL); |
| |
| next = lan_rd(lan966x, QSYS_TAS_GCL_CT_CFG2); |
| next = QSYS_TAS_GCL_CT_CFG2_NEXT_GCL_GET(next); |
| } while (base != next); |
| } |
| |
| return num_free; |
| } |
| |
| static void lan966x_taprio_gcl_setup_entry(struct lan966x_port *port, |
| struct tc_taprio_sched_entry *entry, |
| u32 next_entry) |
| { |
| struct lan966x *lan966x = port->lan966x; |
| |
| /* Setup a single gcl entry */ |
| lan_wr(QSYS_TAS_GCL_CT_CFG_GATE_STATE_SET(entry->gate_mask) | |
| QSYS_TAS_GCL_CT_CFG_HSCH_POS_SET(port->chip_port) | |
| QSYS_TAS_GCL_CT_CFG_OP_TYPE_SET(LAN966X_TAPRIO_GCL_CMD_SET_GATE_STATES), |
| lan966x, QSYS_TAS_GCL_CT_CFG); |
| |
| lan_wr(QSYS_TAS_GCL_CT_CFG2_PORT_PROFILE_SET(port->chip_port) | |
| QSYS_TAS_GCL_CT_CFG2_NEXT_GCL_SET(next_entry), |
| lan966x, QSYS_TAS_GCL_CT_CFG2); |
| |
| lan_wr(entry->interval, lan966x, QSYS_TAS_GCL_TM_CFG); |
| } |
| |
| static int lan966x_taprio_gcl_setup(struct lan966x_port *port, |
| struct tc_taprio_qopt_offload *qopt, |
| int list) |
| { |
| DECLARE_BITMAP(free_list, LAN966X_TAPRIO_NUM_GCL); |
| struct lan966x *lan966x = port->lan966x; |
| u32 i, base, next; |
| |
| if (lan966x_taprio_gcl_free_get(port, free_list) < qopt->num_entries) |
| return -ENOSPC; |
| |
| /* Select list */ |
| lan_rmw(QSYS_TAS_CFG_CTRL_LIST_NUM_SET(list), |
| QSYS_TAS_CFG_CTRL_LIST_NUM, |
| lan966x, QSYS_TAS_CFG_CTRL); |
| |
| /* Setup the address of the first gcl entry */ |
| base = find_first_bit(free_list, LAN966X_TAPRIO_NUM_GCL); |
| lan_rmw(QSYS_TAS_LIST_CFG_LIST_BASE_ADDR_SET(base), |
| QSYS_TAS_LIST_CFG_LIST_BASE_ADDR, |
| lan966x, QSYS_TAS_LIST_CFG); |
| |
| /* Iterate over entries and add them to the gcl list */ |
| next = base; |
| for (i = 0; i < qopt->num_entries; ++i) { |
| lan_rmw(QSYS_TAS_CFG_CTRL_GCL_ENTRY_NUM_SET(next), |
| QSYS_TAS_CFG_CTRL_GCL_ENTRY_NUM, |
| lan966x, QSYS_TAS_CFG_CTRL); |
| |
| /* If the entry is last, point back to the start of the list */ |
| if (i == qopt->num_entries - 1) |
| next = base; |
| else |
| next = find_next_bit(free_list, LAN966X_TAPRIO_NUM_GCL, |
| next + 1); |
| |
| lan966x_taprio_gcl_setup_entry(port, &qopt->entries[i], next); |
| } |
| |
| return 0; |
| } |
| |
| /* Calculate new base_time based on cycle_time. The HW recommends to have the |
| * new base time at least 2 * cycle type + current time |
| */ |
| static void lan966x_taprio_new_base_time(struct lan966x *lan966x, |
| const u32 cycle_time, |
| const ktime_t org_base_time, |
| ktime_t *new_base_time) |
| { |
| ktime_t current_time, threshold_time; |
| struct timespec64 ts; |
| |
| /* Get the current time and calculate the threshold_time */ |
| lan966x_ptp_gettime64(&lan966x->phc[LAN966X_PHC_PORT].info, &ts); |
| current_time = timespec64_to_ktime(ts); |
| threshold_time = current_time + (2 * cycle_time); |
| |
| /* If the org_base_time is in enough in future just use it */ |
| if (org_base_time >= threshold_time) { |
| *new_base_time = org_base_time; |
| return; |
| } |
| |
| /* If the org_base_time is smaller than current_time, calculate the new |
| * base time as following. |
| */ |
| if (org_base_time <= current_time) { |
| u64 tmp = current_time - org_base_time; |
| u32 rem = 0; |
| |
| if (tmp > cycle_time) |
| div_u64_rem(tmp, cycle_time, &rem); |
| rem = cycle_time - rem; |
| *new_base_time = threshold_time + rem; |
| return; |
| } |
| |
| /* The only left place for org_base_time is between current_time and |
| * threshold_time. In this case the new_base_time is calculated like |
| * org_base_time + 2 * cycletime |
| */ |
| *new_base_time = org_base_time + 2 * cycle_time; |
| } |
| |
| int lan966x_taprio_speed_set(struct lan966x_port *port, int speed) |
| { |
| struct lan966x *lan966x = port->lan966x; |
| u8 taprio_speed; |
| |
| switch (speed) { |
| case SPEED_10: |
| taprio_speed = LAN966X_TAPRIO_SPEED_10; |
| break; |
| case SPEED_100: |
| taprio_speed = LAN966X_TAPRIO_SPEED_100; |
| break; |
| case SPEED_1000: |
| taprio_speed = LAN966X_TAPRIO_SPEED_1000; |
| break; |
| case SPEED_2500: |
| taprio_speed = LAN966X_TAPRIO_SPEED_2500; |
| break; |
| default: |
| return -EINVAL; |
| } |
| |
| lan_rmw(QSYS_TAS_PROFILE_CFG_LINK_SPEED_SET(taprio_speed), |
| QSYS_TAS_PROFILE_CFG_LINK_SPEED, |
| lan966x, QSYS_TAS_PROFILE_CFG(port->chip_port)); |
| |
| return 0; |
| } |
| |
| int lan966x_taprio_add(struct lan966x_port *port, |
| struct tc_taprio_qopt_offload *qopt) |
| { |
| struct lan966x *lan966x = port->lan966x; |
| int err, new_list, obs_list; |
| struct timespec64 ts; |
| ktime_t base_time; |
| |
| err = lan966x_taprio_check(qopt); |
| if (err) |
| return err; |
| |
| err = lan966x_taprio_find_list(port, qopt, &new_list, &obs_list); |
| if (err) |
| return err; |
| |
| err = lan966x_taprio_gcl_setup(port, qopt, new_list); |
| if (err) |
| return err; |
| |
| lan966x_taprio_new_base_time(lan966x, qopt->cycle_time, |
| qopt->base_time, &base_time); |
| |
| ts = ktime_to_timespec64(base_time); |
| lan_wr(QSYS_TAS_BT_NSEC_NSEC_SET(ts.tv_nsec), |
| lan966x, QSYS_TAS_BT_NSEC); |
| |
| lan_wr(lower_32_bits(ts.tv_sec), |
| lan966x, QSYS_TAS_BT_SEC_LSB); |
| |
| lan_wr(QSYS_TAS_BT_SEC_MSB_SEC_MSB_SET(upper_32_bits(ts.tv_sec)), |
| lan966x, QSYS_TAS_BT_SEC_MSB); |
| |
| lan_wr(qopt->cycle_time, lan966x, QSYS_TAS_CT_CFG); |
| |
| lan_rmw(QSYS_TAS_STARTUP_CFG_OBSOLETE_IDX_SET(obs_list), |
| QSYS_TAS_STARTUP_CFG_OBSOLETE_IDX, |
| lan966x, QSYS_TAS_STARTUP_CFG); |
| |
| /* Start list processing */ |
| lan_rmw(QSYS_TAS_LST_LIST_STATE_SET(LAN966X_TAPRIO_STATE_ADVANCING), |
| QSYS_TAS_LST_LIST_STATE, |
| lan966x, QSYS_TAS_LST); |
| |
| return err; |
| } |
| |
| int lan966x_taprio_del(struct lan966x_port *port) |
| { |
| return lan966x_taprio_shutdown(port); |
| } |
| |
| void lan966x_taprio_init(struct lan966x *lan966x) |
| { |
| int num_taprio_lists; |
| int p; |
| |
| lan_wr(QSYS_TAS_STM_CFG_REVISIT_DLY_SET((256 * 1000) / |
| lan966x_ptp_get_period_ps()), |
| lan966x, QSYS_TAS_STM_CFG); |
| |
| num_taprio_lists = lan966x->num_phys_ports * |
| LAN966X_TAPRIO_ENTRIES_PER_PORT; |
| |
| /* For now we always use guard band on all queues */ |
| lan_rmw(QSYS_TAS_CFG_CTRL_LIST_NUM_MAX_SET(num_taprio_lists) | |
| QSYS_TAS_CFG_CTRL_ALWAYS_GB_SCH_Q_SET(1), |
| QSYS_TAS_CFG_CTRL_LIST_NUM_MAX | |
| QSYS_TAS_CFG_CTRL_ALWAYS_GB_SCH_Q, |
| lan966x, QSYS_TAS_CFG_CTRL); |
| |
| for (p = 0; p < lan966x->num_phys_ports; p++) |
| lan_rmw(QSYS_TAS_PROFILE_CFG_PORT_NUM_SET(p), |
| QSYS_TAS_PROFILE_CFG_PORT_NUM, |
| lan966x, QSYS_TAS_PROFILE_CFG(p)); |
| } |
| |
| void lan966x_taprio_deinit(struct lan966x *lan966x) |
| { |
| int p; |
| |
| for (p = 0; p < lan966x->num_phys_ports; ++p) { |
| if (!lan966x->ports[p]) |
| continue; |
| |
| lan966x_taprio_del(lan966x->ports[p]); |
| } |
| } |