162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0 262306a36Sopenharmony_ci/* Copyright (c) 2019, Vladimir Oltean <olteanv@gmail.com> 362306a36Sopenharmony_ci */ 462306a36Sopenharmony_ci#include "sja1105.h" 562306a36Sopenharmony_ci 662306a36Sopenharmony_ci#define SJA1105_TAS_CLKSRC_DISABLED 0 762306a36Sopenharmony_ci#define SJA1105_TAS_CLKSRC_STANDALONE 1 862306a36Sopenharmony_ci#define SJA1105_TAS_CLKSRC_AS6802 2 962306a36Sopenharmony_ci#define SJA1105_TAS_CLKSRC_PTP 3 1062306a36Sopenharmony_ci#define SJA1105_GATE_MASK GENMASK_ULL(SJA1105_NUM_TC - 1, 0) 1162306a36Sopenharmony_ci 1262306a36Sopenharmony_ci#define work_to_sja1105_tas(d) \ 1362306a36Sopenharmony_ci container_of((d), struct sja1105_tas_data, tas_work) 1462306a36Sopenharmony_ci#define tas_to_sja1105(d) \ 1562306a36Sopenharmony_ci container_of((d), struct sja1105_private, tas_data) 1662306a36Sopenharmony_ci 1762306a36Sopenharmony_cistatic int sja1105_tas_set_runtime_params(struct sja1105_private *priv) 1862306a36Sopenharmony_ci{ 1962306a36Sopenharmony_ci struct sja1105_tas_data *tas_data = &priv->tas_data; 2062306a36Sopenharmony_ci struct sja1105_gating_config *gating_cfg = &tas_data->gating_cfg; 2162306a36Sopenharmony_ci struct dsa_switch *ds = priv->ds; 2262306a36Sopenharmony_ci s64 earliest_base_time = S64_MAX; 2362306a36Sopenharmony_ci s64 latest_base_time = 0; 2462306a36Sopenharmony_ci s64 its_cycle_time = 0; 2562306a36Sopenharmony_ci s64 max_cycle_time = 0; 2662306a36Sopenharmony_ci int port; 2762306a36Sopenharmony_ci 2862306a36Sopenharmony_ci tas_data->enabled = false; 2962306a36Sopenharmony_ci 3062306a36Sopenharmony_ci for (port = 0; port < ds->num_ports; port++) { 3162306a36Sopenharmony_ci const struct tc_taprio_qopt_offload *offload; 3262306a36Sopenharmony_ci 3362306a36Sopenharmony_ci offload = tas_data->offload[port]; 3462306a36Sopenharmony_ci if (!offload) 3562306a36Sopenharmony_ci continue; 3662306a36Sopenharmony_ci 3762306a36Sopenharmony_ci tas_data->enabled = true; 3862306a36Sopenharmony_ci 3962306a36Sopenharmony_ci if (max_cycle_time < offload->cycle_time) 4062306a36Sopenharmony_ci max_cycle_time = offload->cycle_time; 4162306a36Sopenharmony_ci if (latest_base_time < offload->base_time) 4262306a36Sopenharmony_ci latest_base_time = offload->base_time; 4362306a36Sopenharmony_ci if (earliest_base_time > offload->base_time) { 4462306a36Sopenharmony_ci earliest_base_time = offload->base_time; 4562306a36Sopenharmony_ci its_cycle_time = offload->cycle_time; 4662306a36Sopenharmony_ci } 4762306a36Sopenharmony_ci } 4862306a36Sopenharmony_ci 4962306a36Sopenharmony_ci if (!list_empty(&gating_cfg->entries)) { 5062306a36Sopenharmony_ci tas_data->enabled = true; 5162306a36Sopenharmony_ci 5262306a36Sopenharmony_ci if (max_cycle_time < gating_cfg->cycle_time) 5362306a36Sopenharmony_ci max_cycle_time = gating_cfg->cycle_time; 5462306a36Sopenharmony_ci if (latest_base_time < gating_cfg->base_time) 5562306a36Sopenharmony_ci latest_base_time = gating_cfg->base_time; 5662306a36Sopenharmony_ci if (earliest_base_time > gating_cfg->base_time) { 5762306a36Sopenharmony_ci earliest_base_time = gating_cfg->base_time; 5862306a36Sopenharmony_ci its_cycle_time = gating_cfg->cycle_time; 5962306a36Sopenharmony_ci } 6062306a36Sopenharmony_ci } 6162306a36Sopenharmony_ci 6262306a36Sopenharmony_ci if (!tas_data->enabled) 6362306a36Sopenharmony_ci return 0; 6462306a36Sopenharmony_ci 6562306a36Sopenharmony_ci /* Roll the earliest base time over until it is in a comparable 6662306a36Sopenharmony_ci * time base with the latest, then compare their deltas. 6762306a36Sopenharmony_ci * We want to enforce that all ports' base times are within 6862306a36Sopenharmony_ci * SJA1105_TAS_MAX_DELTA 200ns cycles of one another. 6962306a36Sopenharmony_ci */ 7062306a36Sopenharmony_ci earliest_base_time = future_base_time(earliest_base_time, 7162306a36Sopenharmony_ci its_cycle_time, 7262306a36Sopenharmony_ci latest_base_time); 7362306a36Sopenharmony_ci while (earliest_base_time > latest_base_time) 7462306a36Sopenharmony_ci earliest_base_time -= its_cycle_time; 7562306a36Sopenharmony_ci if (latest_base_time - earliest_base_time > 7662306a36Sopenharmony_ci sja1105_delta_to_ns(SJA1105_TAS_MAX_DELTA)) { 7762306a36Sopenharmony_ci dev_err(ds->dev, 7862306a36Sopenharmony_ci "Base times too far apart: min %llu max %llu\n", 7962306a36Sopenharmony_ci earliest_base_time, latest_base_time); 8062306a36Sopenharmony_ci return -ERANGE; 8162306a36Sopenharmony_ci } 8262306a36Sopenharmony_ci 8362306a36Sopenharmony_ci tas_data->earliest_base_time = earliest_base_time; 8462306a36Sopenharmony_ci tas_data->max_cycle_time = max_cycle_time; 8562306a36Sopenharmony_ci 8662306a36Sopenharmony_ci dev_dbg(ds->dev, "earliest base time %lld ns\n", earliest_base_time); 8762306a36Sopenharmony_ci dev_dbg(ds->dev, "latest base time %lld ns\n", latest_base_time); 8862306a36Sopenharmony_ci dev_dbg(ds->dev, "longest cycle time %lld ns\n", max_cycle_time); 8962306a36Sopenharmony_ci 9062306a36Sopenharmony_ci return 0; 9162306a36Sopenharmony_ci} 9262306a36Sopenharmony_ci 9362306a36Sopenharmony_ci/* Lo and behold: the egress scheduler from hell. 9462306a36Sopenharmony_ci * 9562306a36Sopenharmony_ci * At the hardware level, the Time-Aware Shaper holds a global linear arrray of 9662306a36Sopenharmony_ci * all schedule entries for all ports. These are the Gate Control List (GCL) 9762306a36Sopenharmony_ci * entries, let's call them "timeslots" for short. This linear array of 9862306a36Sopenharmony_ci * timeslots is held in BLK_IDX_SCHEDULE. 9962306a36Sopenharmony_ci * 10062306a36Sopenharmony_ci * Then there are a maximum of 8 "execution threads" inside the switch, which 10162306a36Sopenharmony_ci * iterate cyclically through the "schedule". Each "cycle" has an entry point 10262306a36Sopenharmony_ci * and an exit point, both being timeslot indices in the schedule table. The 10362306a36Sopenharmony_ci * hardware calls each cycle a "subschedule". 10462306a36Sopenharmony_ci * 10562306a36Sopenharmony_ci * Subschedule (cycle) i starts when 10662306a36Sopenharmony_ci * ptpclkval >= ptpschtm + BLK_IDX_SCHEDULE_ENTRY_POINTS[i].delta. 10762306a36Sopenharmony_ci * 10862306a36Sopenharmony_ci * The hardware scheduler iterates BLK_IDX_SCHEDULE with a k ranging from 10962306a36Sopenharmony_ci * k = BLK_IDX_SCHEDULE_ENTRY_POINTS[i].address to 11062306a36Sopenharmony_ci * k = BLK_IDX_SCHEDULE_PARAMS.subscheind[i] 11162306a36Sopenharmony_ci * 11262306a36Sopenharmony_ci * For each schedule entry (timeslot) k, the engine executes the gate control 11362306a36Sopenharmony_ci * list entry for the duration of BLK_IDX_SCHEDULE[k].delta. 11462306a36Sopenharmony_ci * 11562306a36Sopenharmony_ci * +---------+ 11662306a36Sopenharmony_ci * | | BLK_IDX_SCHEDULE_ENTRY_POINTS_PARAMS 11762306a36Sopenharmony_ci * +---------+ 11862306a36Sopenharmony_ci * | 11962306a36Sopenharmony_ci * +-----------------+ 12062306a36Sopenharmony_ci * | .actsubsch 12162306a36Sopenharmony_ci * BLK_IDX_SCHEDULE_ENTRY_POINTS v 12262306a36Sopenharmony_ci * +-------+-------+ 12362306a36Sopenharmony_ci * |cycle 0|cycle 1| 12462306a36Sopenharmony_ci * +-------+-------+ 12562306a36Sopenharmony_ci * | | | | 12662306a36Sopenharmony_ci * +----------------+ | | +-------------------------------------+ 12762306a36Sopenharmony_ci * | .subschindx | | .subschindx | 12862306a36Sopenharmony_ci * | | +---------------+ | 12962306a36Sopenharmony_ci * | .address | .address | | 13062306a36Sopenharmony_ci * | | | | 13162306a36Sopenharmony_ci * | | | | 13262306a36Sopenharmony_ci * | BLK_IDX_SCHEDULE v v | 13362306a36Sopenharmony_ci * | +-------+-------+-------+-------+-------+------+ | 13462306a36Sopenharmony_ci * | |entry 0|entry 1|entry 2|entry 3|entry 4|entry5| | 13562306a36Sopenharmony_ci * | +-------+-------+-------+-------+-------+------+ | 13662306a36Sopenharmony_ci * | ^ ^ ^ ^ | 13762306a36Sopenharmony_ci * | | | | | | 13862306a36Sopenharmony_ci * | +-------------------------+ | | | | 13962306a36Sopenharmony_ci * | | +-------------------------------+ | | | 14062306a36Sopenharmony_ci * | | | +-------------------+ | | 14162306a36Sopenharmony_ci * | | | | | | 14262306a36Sopenharmony_ci * | +---------------------------------------------------------------+ | 14362306a36Sopenharmony_ci * | |subscheind[0]<=subscheind[1]<=subscheind[2]<=...<=subscheind[7]| | 14462306a36Sopenharmony_ci * | +---------------------------------------------------------------+ | 14562306a36Sopenharmony_ci * | ^ ^ BLK_IDX_SCHEDULE_PARAMS | 14662306a36Sopenharmony_ci * | | | | 14762306a36Sopenharmony_ci * +--------+ +-------------------------------------------+ 14862306a36Sopenharmony_ci * 14962306a36Sopenharmony_ci * In the above picture there are two subschedules (cycles): 15062306a36Sopenharmony_ci * 15162306a36Sopenharmony_ci * - cycle 0: iterates the schedule table from 0 to 2 (and back) 15262306a36Sopenharmony_ci * - cycle 1: iterates the schedule table from 3 to 5 (and back) 15362306a36Sopenharmony_ci * 15462306a36Sopenharmony_ci * All other possible execution threads must be marked as unused by making 15562306a36Sopenharmony_ci * their "subschedule end index" (subscheind) equal to the last valid 15662306a36Sopenharmony_ci * subschedule's end index (in this case 5). 15762306a36Sopenharmony_ci */ 15862306a36Sopenharmony_ciint sja1105_init_scheduling(struct sja1105_private *priv) 15962306a36Sopenharmony_ci{ 16062306a36Sopenharmony_ci struct sja1105_schedule_entry_points_entry *schedule_entry_points; 16162306a36Sopenharmony_ci struct sja1105_schedule_entry_points_params_entry 16262306a36Sopenharmony_ci *schedule_entry_points_params; 16362306a36Sopenharmony_ci struct sja1105_schedule_params_entry *schedule_params; 16462306a36Sopenharmony_ci struct sja1105_tas_data *tas_data = &priv->tas_data; 16562306a36Sopenharmony_ci struct sja1105_gating_config *gating_cfg = &tas_data->gating_cfg; 16662306a36Sopenharmony_ci struct sja1105_schedule_entry *schedule; 16762306a36Sopenharmony_ci struct dsa_switch *ds = priv->ds; 16862306a36Sopenharmony_ci struct sja1105_table *table; 16962306a36Sopenharmony_ci int schedule_start_idx; 17062306a36Sopenharmony_ci s64 entry_point_delta; 17162306a36Sopenharmony_ci int schedule_end_idx; 17262306a36Sopenharmony_ci int num_entries = 0; 17362306a36Sopenharmony_ci int num_cycles = 0; 17462306a36Sopenharmony_ci int cycle = 0; 17562306a36Sopenharmony_ci int i, k = 0; 17662306a36Sopenharmony_ci int port, rc; 17762306a36Sopenharmony_ci 17862306a36Sopenharmony_ci rc = sja1105_tas_set_runtime_params(priv); 17962306a36Sopenharmony_ci if (rc < 0) 18062306a36Sopenharmony_ci return rc; 18162306a36Sopenharmony_ci 18262306a36Sopenharmony_ci /* Discard previous Schedule Table */ 18362306a36Sopenharmony_ci table = &priv->static_config.tables[BLK_IDX_SCHEDULE]; 18462306a36Sopenharmony_ci if (table->entry_count) { 18562306a36Sopenharmony_ci kfree(table->entries); 18662306a36Sopenharmony_ci table->entry_count = 0; 18762306a36Sopenharmony_ci } 18862306a36Sopenharmony_ci 18962306a36Sopenharmony_ci /* Discard previous Schedule Entry Points Parameters Table */ 19062306a36Sopenharmony_ci table = &priv->static_config.tables[BLK_IDX_SCHEDULE_ENTRY_POINTS_PARAMS]; 19162306a36Sopenharmony_ci if (table->entry_count) { 19262306a36Sopenharmony_ci kfree(table->entries); 19362306a36Sopenharmony_ci table->entry_count = 0; 19462306a36Sopenharmony_ci } 19562306a36Sopenharmony_ci 19662306a36Sopenharmony_ci /* Discard previous Schedule Parameters Table */ 19762306a36Sopenharmony_ci table = &priv->static_config.tables[BLK_IDX_SCHEDULE_PARAMS]; 19862306a36Sopenharmony_ci if (table->entry_count) { 19962306a36Sopenharmony_ci kfree(table->entries); 20062306a36Sopenharmony_ci table->entry_count = 0; 20162306a36Sopenharmony_ci } 20262306a36Sopenharmony_ci 20362306a36Sopenharmony_ci /* Discard previous Schedule Entry Points Table */ 20462306a36Sopenharmony_ci table = &priv->static_config.tables[BLK_IDX_SCHEDULE_ENTRY_POINTS]; 20562306a36Sopenharmony_ci if (table->entry_count) { 20662306a36Sopenharmony_ci kfree(table->entries); 20762306a36Sopenharmony_ci table->entry_count = 0; 20862306a36Sopenharmony_ci } 20962306a36Sopenharmony_ci 21062306a36Sopenharmony_ci /* Figure out the dimensioning of the problem */ 21162306a36Sopenharmony_ci for (port = 0; port < ds->num_ports; port++) { 21262306a36Sopenharmony_ci if (tas_data->offload[port]) { 21362306a36Sopenharmony_ci num_entries += tas_data->offload[port]->num_entries; 21462306a36Sopenharmony_ci num_cycles++; 21562306a36Sopenharmony_ci } 21662306a36Sopenharmony_ci } 21762306a36Sopenharmony_ci 21862306a36Sopenharmony_ci if (!list_empty(&gating_cfg->entries)) { 21962306a36Sopenharmony_ci num_entries += gating_cfg->num_entries; 22062306a36Sopenharmony_ci num_cycles++; 22162306a36Sopenharmony_ci } 22262306a36Sopenharmony_ci 22362306a36Sopenharmony_ci /* Nothing to do */ 22462306a36Sopenharmony_ci if (!num_cycles) 22562306a36Sopenharmony_ci return 0; 22662306a36Sopenharmony_ci 22762306a36Sopenharmony_ci /* Pre-allocate space in the static config tables */ 22862306a36Sopenharmony_ci 22962306a36Sopenharmony_ci /* Schedule Table */ 23062306a36Sopenharmony_ci table = &priv->static_config.tables[BLK_IDX_SCHEDULE]; 23162306a36Sopenharmony_ci table->entries = kcalloc(num_entries, table->ops->unpacked_entry_size, 23262306a36Sopenharmony_ci GFP_KERNEL); 23362306a36Sopenharmony_ci if (!table->entries) 23462306a36Sopenharmony_ci return -ENOMEM; 23562306a36Sopenharmony_ci table->entry_count = num_entries; 23662306a36Sopenharmony_ci schedule = table->entries; 23762306a36Sopenharmony_ci 23862306a36Sopenharmony_ci /* Schedule Points Parameters Table */ 23962306a36Sopenharmony_ci table = &priv->static_config.tables[BLK_IDX_SCHEDULE_ENTRY_POINTS_PARAMS]; 24062306a36Sopenharmony_ci table->entries = kcalloc(SJA1105_MAX_SCHEDULE_ENTRY_POINTS_PARAMS_COUNT, 24162306a36Sopenharmony_ci table->ops->unpacked_entry_size, GFP_KERNEL); 24262306a36Sopenharmony_ci if (!table->entries) 24362306a36Sopenharmony_ci /* Previously allocated memory will be freed automatically in 24462306a36Sopenharmony_ci * sja1105_static_config_free. This is true for all early 24562306a36Sopenharmony_ci * returns below. 24662306a36Sopenharmony_ci */ 24762306a36Sopenharmony_ci return -ENOMEM; 24862306a36Sopenharmony_ci table->entry_count = SJA1105_MAX_SCHEDULE_ENTRY_POINTS_PARAMS_COUNT; 24962306a36Sopenharmony_ci schedule_entry_points_params = table->entries; 25062306a36Sopenharmony_ci 25162306a36Sopenharmony_ci /* Schedule Parameters Table */ 25262306a36Sopenharmony_ci table = &priv->static_config.tables[BLK_IDX_SCHEDULE_PARAMS]; 25362306a36Sopenharmony_ci table->entries = kcalloc(SJA1105_MAX_SCHEDULE_PARAMS_COUNT, 25462306a36Sopenharmony_ci table->ops->unpacked_entry_size, GFP_KERNEL); 25562306a36Sopenharmony_ci if (!table->entries) 25662306a36Sopenharmony_ci return -ENOMEM; 25762306a36Sopenharmony_ci table->entry_count = SJA1105_MAX_SCHEDULE_PARAMS_COUNT; 25862306a36Sopenharmony_ci schedule_params = table->entries; 25962306a36Sopenharmony_ci 26062306a36Sopenharmony_ci /* Schedule Entry Points Table */ 26162306a36Sopenharmony_ci table = &priv->static_config.tables[BLK_IDX_SCHEDULE_ENTRY_POINTS]; 26262306a36Sopenharmony_ci table->entries = kcalloc(num_cycles, table->ops->unpacked_entry_size, 26362306a36Sopenharmony_ci GFP_KERNEL); 26462306a36Sopenharmony_ci if (!table->entries) 26562306a36Sopenharmony_ci return -ENOMEM; 26662306a36Sopenharmony_ci table->entry_count = num_cycles; 26762306a36Sopenharmony_ci schedule_entry_points = table->entries; 26862306a36Sopenharmony_ci 26962306a36Sopenharmony_ci /* Finally start populating the static config tables */ 27062306a36Sopenharmony_ci schedule_entry_points_params->clksrc = SJA1105_TAS_CLKSRC_PTP; 27162306a36Sopenharmony_ci schedule_entry_points_params->actsubsch = num_cycles - 1; 27262306a36Sopenharmony_ci 27362306a36Sopenharmony_ci for (port = 0; port < ds->num_ports; port++) { 27462306a36Sopenharmony_ci const struct tc_taprio_qopt_offload *offload; 27562306a36Sopenharmony_ci /* Relative base time */ 27662306a36Sopenharmony_ci s64 rbt; 27762306a36Sopenharmony_ci 27862306a36Sopenharmony_ci offload = tas_data->offload[port]; 27962306a36Sopenharmony_ci if (!offload) 28062306a36Sopenharmony_ci continue; 28162306a36Sopenharmony_ci 28262306a36Sopenharmony_ci schedule_start_idx = k; 28362306a36Sopenharmony_ci schedule_end_idx = k + offload->num_entries - 1; 28462306a36Sopenharmony_ci /* This is the base time expressed as a number of TAS ticks 28562306a36Sopenharmony_ci * relative to PTPSCHTM, which we'll (perhaps improperly) call 28662306a36Sopenharmony_ci * the operational base time. 28762306a36Sopenharmony_ci */ 28862306a36Sopenharmony_ci rbt = future_base_time(offload->base_time, 28962306a36Sopenharmony_ci offload->cycle_time, 29062306a36Sopenharmony_ci tas_data->earliest_base_time); 29162306a36Sopenharmony_ci rbt -= tas_data->earliest_base_time; 29262306a36Sopenharmony_ci /* UM10944.pdf 4.2.2. Schedule Entry Points table says that 29362306a36Sopenharmony_ci * delta cannot be zero, which is shitty. Advance all relative 29462306a36Sopenharmony_ci * base times by 1 TAS delta, so that even the earliest base 29562306a36Sopenharmony_ci * time becomes 1 in relative terms. Then start the operational 29662306a36Sopenharmony_ci * base time (PTPSCHTM) one TAS delta earlier than planned. 29762306a36Sopenharmony_ci */ 29862306a36Sopenharmony_ci entry_point_delta = ns_to_sja1105_delta(rbt) + 1; 29962306a36Sopenharmony_ci 30062306a36Sopenharmony_ci schedule_entry_points[cycle].subschindx = cycle; 30162306a36Sopenharmony_ci schedule_entry_points[cycle].delta = entry_point_delta; 30262306a36Sopenharmony_ci schedule_entry_points[cycle].address = schedule_start_idx; 30362306a36Sopenharmony_ci 30462306a36Sopenharmony_ci /* The subschedule end indices need to be 30562306a36Sopenharmony_ci * monotonically increasing. 30662306a36Sopenharmony_ci */ 30762306a36Sopenharmony_ci for (i = cycle; i < 8; i++) 30862306a36Sopenharmony_ci schedule_params->subscheind[i] = schedule_end_idx; 30962306a36Sopenharmony_ci 31062306a36Sopenharmony_ci for (i = 0; i < offload->num_entries; i++, k++) { 31162306a36Sopenharmony_ci s64 delta_ns = offload->entries[i].interval; 31262306a36Sopenharmony_ci 31362306a36Sopenharmony_ci schedule[k].delta = ns_to_sja1105_delta(delta_ns); 31462306a36Sopenharmony_ci schedule[k].destports = BIT(port); 31562306a36Sopenharmony_ci schedule[k].resmedia_en = true; 31662306a36Sopenharmony_ci schedule[k].resmedia = SJA1105_GATE_MASK & 31762306a36Sopenharmony_ci ~offload->entries[i].gate_mask; 31862306a36Sopenharmony_ci } 31962306a36Sopenharmony_ci cycle++; 32062306a36Sopenharmony_ci } 32162306a36Sopenharmony_ci 32262306a36Sopenharmony_ci if (!list_empty(&gating_cfg->entries)) { 32362306a36Sopenharmony_ci struct sja1105_gate_entry *e; 32462306a36Sopenharmony_ci 32562306a36Sopenharmony_ci /* Relative base time */ 32662306a36Sopenharmony_ci s64 rbt; 32762306a36Sopenharmony_ci 32862306a36Sopenharmony_ci schedule_start_idx = k; 32962306a36Sopenharmony_ci schedule_end_idx = k + gating_cfg->num_entries - 1; 33062306a36Sopenharmony_ci rbt = future_base_time(gating_cfg->base_time, 33162306a36Sopenharmony_ci gating_cfg->cycle_time, 33262306a36Sopenharmony_ci tas_data->earliest_base_time); 33362306a36Sopenharmony_ci rbt -= tas_data->earliest_base_time; 33462306a36Sopenharmony_ci entry_point_delta = ns_to_sja1105_delta(rbt) + 1; 33562306a36Sopenharmony_ci 33662306a36Sopenharmony_ci schedule_entry_points[cycle].subschindx = cycle; 33762306a36Sopenharmony_ci schedule_entry_points[cycle].delta = entry_point_delta; 33862306a36Sopenharmony_ci schedule_entry_points[cycle].address = schedule_start_idx; 33962306a36Sopenharmony_ci 34062306a36Sopenharmony_ci for (i = cycle; i < 8; i++) 34162306a36Sopenharmony_ci schedule_params->subscheind[i] = schedule_end_idx; 34262306a36Sopenharmony_ci 34362306a36Sopenharmony_ci list_for_each_entry(e, &gating_cfg->entries, list) { 34462306a36Sopenharmony_ci schedule[k].delta = ns_to_sja1105_delta(e->interval); 34562306a36Sopenharmony_ci schedule[k].destports = e->rule->vl.destports; 34662306a36Sopenharmony_ci schedule[k].setvalid = true; 34762306a36Sopenharmony_ci schedule[k].txen = true; 34862306a36Sopenharmony_ci schedule[k].vlindex = e->rule->vl.sharindx; 34962306a36Sopenharmony_ci schedule[k].winstindex = e->rule->vl.sharindx; 35062306a36Sopenharmony_ci if (e->gate_state) /* Gate open */ 35162306a36Sopenharmony_ci schedule[k].winst = true; 35262306a36Sopenharmony_ci else /* Gate closed */ 35362306a36Sopenharmony_ci schedule[k].winend = true; 35462306a36Sopenharmony_ci k++; 35562306a36Sopenharmony_ci } 35662306a36Sopenharmony_ci } 35762306a36Sopenharmony_ci 35862306a36Sopenharmony_ci return 0; 35962306a36Sopenharmony_ci} 36062306a36Sopenharmony_ci 36162306a36Sopenharmony_ci/* Be there 2 port subschedules, each executing an arbitrary number of gate 36262306a36Sopenharmony_ci * open/close events cyclically. 36362306a36Sopenharmony_ci * None of those gate events must ever occur at the exact same time, otherwise 36462306a36Sopenharmony_ci * the switch is known to act in exotically strange ways. 36562306a36Sopenharmony_ci * However the hardware doesn't bother performing these integrity checks. 36662306a36Sopenharmony_ci * So here we are with the task of validating whether the new @admin offload 36762306a36Sopenharmony_ci * has any conflict with the already established TAS configuration in 36862306a36Sopenharmony_ci * tas_data->offload. We already know the other ports are in harmony with one 36962306a36Sopenharmony_ci * another, otherwise we wouldn't have saved them. 37062306a36Sopenharmony_ci * Each gate event executes periodically, with a period of @cycle_time and a 37162306a36Sopenharmony_ci * phase given by its cycle's @base_time plus its offset within the cycle 37262306a36Sopenharmony_ci * (which in turn is given by the length of the events prior to it). 37362306a36Sopenharmony_ci * There are two aspects to possible collisions: 37462306a36Sopenharmony_ci * - Collisions within one cycle's (actually the longest cycle's) time frame. 37562306a36Sopenharmony_ci * For that, we need to compare the cartesian product of each possible 37662306a36Sopenharmony_ci * occurrence of each event within one cycle time. 37762306a36Sopenharmony_ci * - Collisions in the future. Events may not collide within one cycle time, 37862306a36Sopenharmony_ci * but if two port schedules don't have the same periodicity (aka the cycle 37962306a36Sopenharmony_ci * times aren't multiples of one another), they surely will some time in the 38062306a36Sopenharmony_ci * future (actually they will collide an infinite amount of times). 38162306a36Sopenharmony_ci */ 38262306a36Sopenharmony_cistatic bool 38362306a36Sopenharmony_cisja1105_tas_check_conflicts(struct sja1105_private *priv, int port, 38462306a36Sopenharmony_ci const struct tc_taprio_qopt_offload *admin) 38562306a36Sopenharmony_ci{ 38662306a36Sopenharmony_ci struct sja1105_tas_data *tas_data = &priv->tas_data; 38762306a36Sopenharmony_ci const struct tc_taprio_qopt_offload *offload; 38862306a36Sopenharmony_ci s64 max_cycle_time, min_cycle_time; 38962306a36Sopenharmony_ci s64 delta1, delta2; 39062306a36Sopenharmony_ci s64 rbt1, rbt2; 39162306a36Sopenharmony_ci s64 stop_time; 39262306a36Sopenharmony_ci s64 t1, t2; 39362306a36Sopenharmony_ci int i, j; 39462306a36Sopenharmony_ci s32 rem; 39562306a36Sopenharmony_ci 39662306a36Sopenharmony_ci offload = tas_data->offload[port]; 39762306a36Sopenharmony_ci if (!offload) 39862306a36Sopenharmony_ci return false; 39962306a36Sopenharmony_ci 40062306a36Sopenharmony_ci /* Check if the two cycle times are multiples of one another. 40162306a36Sopenharmony_ci * If they aren't, then they will surely collide. 40262306a36Sopenharmony_ci */ 40362306a36Sopenharmony_ci max_cycle_time = max(offload->cycle_time, admin->cycle_time); 40462306a36Sopenharmony_ci min_cycle_time = min(offload->cycle_time, admin->cycle_time); 40562306a36Sopenharmony_ci div_s64_rem(max_cycle_time, min_cycle_time, &rem); 40662306a36Sopenharmony_ci if (rem) 40762306a36Sopenharmony_ci return true; 40862306a36Sopenharmony_ci 40962306a36Sopenharmony_ci /* Calculate the "reduced" base time of each of the two cycles 41062306a36Sopenharmony_ci * (transposed back as close to 0 as possible) by dividing to 41162306a36Sopenharmony_ci * the cycle time. 41262306a36Sopenharmony_ci */ 41362306a36Sopenharmony_ci div_s64_rem(offload->base_time, offload->cycle_time, &rem); 41462306a36Sopenharmony_ci rbt1 = rem; 41562306a36Sopenharmony_ci 41662306a36Sopenharmony_ci div_s64_rem(admin->base_time, admin->cycle_time, &rem); 41762306a36Sopenharmony_ci rbt2 = rem; 41862306a36Sopenharmony_ci 41962306a36Sopenharmony_ci stop_time = max_cycle_time + max(rbt1, rbt2); 42062306a36Sopenharmony_ci 42162306a36Sopenharmony_ci /* delta1 is the relative base time of each GCL entry within 42262306a36Sopenharmony_ci * the established ports' TAS config. 42362306a36Sopenharmony_ci */ 42462306a36Sopenharmony_ci for (i = 0, delta1 = 0; 42562306a36Sopenharmony_ci i < offload->num_entries; 42662306a36Sopenharmony_ci delta1 += offload->entries[i].interval, i++) { 42762306a36Sopenharmony_ci /* delta2 is the relative base time of each GCL entry 42862306a36Sopenharmony_ci * within the newly added TAS config. 42962306a36Sopenharmony_ci */ 43062306a36Sopenharmony_ci for (j = 0, delta2 = 0; 43162306a36Sopenharmony_ci j < admin->num_entries; 43262306a36Sopenharmony_ci delta2 += admin->entries[j].interval, j++) { 43362306a36Sopenharmony_ci /* t1 follows all possible occurrences of the 43462306a36Sopenharmony_ci * established ports' GCL entry i within the 43562306a36Sopenharmony_ci * first cycle time. 43662306a36Sopenharmony_ci */ 43762306a36Sopenharmony_ci for (t1 = rbt1 + delta1; 43862306a36Sopenharmony_ci t1 <= stop_time; 43962306a36Sopenharmony_ci t1 += offload->cycle_time) { 44062306a36Sopenharmony_ci /* t2 follows all possible occurrences 44162306a36Sopenharmony_ci * of the newly added GCL entry j 44262306a36Sopenharmony_ci * within the first cycle time. 44362306a36Sopenharmony_ci */ 44462306a36Sopenharmony_ci for (t2 = rbt2 + delta2; 44562306a36Sopenharmony_ci t2 <= stop_time; 44662306a36Sopenharmony_ci t2 += admin->cycle_time) { 44762306a36Sopenharmony_ci if (t1 == t2) { 44862306a36Sopenharmony_ci dev_warn(priv->ds->dev, 44962306a36Sopenharmony_ci "GCL entry %d collides with entry %d of port %d\n", 45062306a36Sopenharmony_ci j, i, port); 45162306a36Sopenharmony_ci return true; 45262306a36Sopenharmony_ci } 45362306a36Sopenharmony_ci } 45462306a36Sopenharmony_ci } 45562306a36Sopenharmony_ci } 45662306a36Sopenharmony_ci } 45762306a36Sopenharmony_ci 45862306a36Sopenharmony_ci return false; 45962306a36Sopenharmony_ci} 46062306a36Sopenharmony_ci 46162306a36Sopenharmony_ci/* Check the tc-taprio configuration on @port for conflicts with the tc-gate 46262306a36Sopenharmony_ci * global subschedule. If @port is -1, check it against all ports. 46362306a36Sopenharmony_ci * To reuse the sja1105_tas_check_conflicts logic without refactoring it, 46462306a36Sopenharmony_ci * convert the gating configuration to a dummy tc-taprio offload structure. 46562306a36Sopenharmony_ci */ 46662306a36Sopenharmony_cibool sja1105_gating_check_conflicts(struct sja1105_private *priv, int port, 46762306a36Sopenharmony_ci struct netlink_ext_ack *extack) 46862306a36Sopenharmony_ci{ 46962306a36Sopenharmony_ci struct sja1105_gating_config *gating_cfg = &priv->tas_data.gating_cfg; 47062306a36Sopenharmony_ci size_t num_entries = gating_cfg->num_entries; 47162306a36Sopenharmony_ci struct tc_taprio_qopt_offload *dummy; 47262306a36Sopenharmony_ci struct dsa_switch *ds = priv->ds; 47362306a36Sopenharmony_ci struct sja1105_gate_entry *e; 47462306a36Sopenharmony_ci bool conflict; 47562306a36Sopenharmony_ci int i = 0; 47662306a36Sopenharmony_ci 47762306a36Sopenharmony_ci if (list_empty(&gating_cfg->entries)) 47862306a36Sopenharmony_ci return false; 47962306a36Sopenharmony_ci 48062306a36Sopenharmony_ci dummy = kzalloc(struct_size(dummy, entries, num_entries), GFP_KERNEL); 48162306a36Sopenharmony_ci if (!dummy) { 48262306a36Sopenharmony_ci NL_SET_ERR_MSG_MOD(extack, "Failed to allocate memory"); 48362306a36Sopenharmony_ci return true; 48462306a36Sopenharmony_ci } 48562306a36Sopenharmony_ci 48662306a36Sopenharmony_ci dummy->num_entries = num_entries; 48762306a36Sopenharmony_ci dummy->base_time = gating_cfg->base_time; 48862306a36Sopenharmony_ci dummy->cycle_time = gating_cfg->cycle_time; 48962306a36Sopenharmony_ci 49062306a36Sopenharmony_ci list_for_each_entry(e, &gating_cfg->entries, list) 49162306a36Sopenharmony_ci dummy->entries[i++].interval = e->interval; 49262306a36Sopenharmony_ci 49362306a36Sopenharmony_ci if (port != -1) { 49462306a36Sopenharmony_ci conflict = sja1105_tas_check_conflicts(priv, port, dummy); 49562306a36Sopenharmony_ci } else { 49662306a36Sopenharmony_ci for (port = 0; port < ds->num_ports; port++) { 49762306a36Sopenharmony_ci conflict = sja1105_tas_check_conflicts(priv, port, 49862306a36Sopenharmony_ci dummy); 49962306a36Sopenharmony_ci if (conflict) 50062306a36Sopenharmony_ci break; 50162306a36Sopenharmony_ci } 50262306a36Sopenharmony_ci } 50362306a36Sopenharmony_ci 50462306a36Sopenharmony_ci kfree(dummy); 50562306a36Sopenharmony_ci 50662306a36Sopenharmony_ci return conflict; 50762306a36Sopenharmony_ci} 50862306a36Sopenharmony_ci 50962306a36Sopenharmony_ciint sja1105_setup_tc_taprio(struct dsa_switch *ds, int port, 51062306a36Sopenharmony_ci struct tc_taprio_qopt_offload *admin) 51162306a36Sopenharmony_ci{ 51262306a36Sopenharmony_ci struct sja1105_private *priv = ds->priv; 51362306a36Sopenharmony_ci struct sja1105_tas_data *tas_data = &priv->tas_data; 51462306a36Sopenharmony_ci int other_port, rc, i; 51562306a36Sopenharmony_ci 51662306a36Sopenharmony_ci /* Can't change an already configured port (must delete qdisc first). 51762306a36Sopenharmony_ci * Can't delete the qdisc from an unconfigured port. 51862306a36Sopenharmony_ci */ 51962306a36Sopenharmony_ci if ((!!tas_data->offload[port] && admin->cmd == TAPRIO_CMD_REPLACE) || 52062306a36Sopenharmony_ci (!tas_data->offload[port] && admin->cmd == TAPRIO_CMD_DESTROY)) 52162306a36Sopenharmony_ci return -EINVAL; 52262306a36Sopenharmony_ci 52362306a36Sopenharmony_ci if (admin->cmd == TAPRIO_CMD_DESTROY) { 52462306a36Sopenharmony_ci taprio_offload_free(tas_data->offload[port]); 52562306a36Sopenharmony_ci tas_data->offload[port] = NULL; 52662306a36Sopenharmony_ci 52762306a36Sopenharmony_ci rc = sja1105_init_scheduling(priv); 52862306a36Sopenharmony_ci if (rc < 0) 52962306a36Sopenharmony_ci return rc; 53062306a36Sopenharmony_ci 53162306a36Sopenharmony_ci return sja1105_static_config_reload(priv, SJA1105_SCHEDULING); 53262306a36Sopenharmony_ci } else if (admin->cmd != TAPRIO_CMD_REPLACE) { 53362306a36Sopenharmony_ci return -EOPNOTSUPP; 53462306a36Sopenharmony_ci } 53562306a36Sopenharmony_ci 53662306a36Sopenharmony_ci /* The cycle time extension is the amount of time the last cycle from 53762306a36Sopenharmony_ci * the old OPER needs to be extended in order to phase-align with the 53862306a36Sopenharmony_ci * base time of the ADMIN when that becomes the new OPER. 53962306a36Sopenharmony_ci * But of course our switch needs to be reset to switch-over between 54062306a36Sopenharmony_ci * the ADMIN and the OPER configs - so much for a seamless transition. 54162306a36Sopenharmony_ci * So don't add insult over injury and just say we don't support cycle 54262306a36Sopenharmony_ci * time extension. 54362306a36Sopenharmony_ci */ 54462306a36Sopenharmony_ci if (admin->cycle_time_extension) 54562306a36Sopenharmony_ci return -ENOTSUPP; 54662306a36Sopenharmony_ci 54762306a36Sopenharmony_ci for (i = 0; i < admin->num_entries; i++) { 54862306a36Sopenharmony_ci s64 delta_ns = admin->entries[i].interval; 54962306a36Sopenharmony_ci s64 delta_cycles = ns_to_sja1105_delta(delta_ns); 55062306a36Sopenharmony_ci bool too_long, too_short; 55162306a36Sopenharmony_ci 55262306a36Sopenharmony_ci too_long = (delta_cycles >= SJA1105_TAS_MAX_DELTA); 55362306a36Sopenharmony_ci too_short = (delta_cycles == 0); 55462306a36Sopenharmony_ci if (too_long || too_short) { 55562306a36Sopenharmony_ci dev_err(priv->ds->dev, 55662306a36Sopenharmony_ci "Interval %llu too %s for GCL entry %d\n", 55762306a36Sopenharmony_ci delta_ns, too_long ? "long" : "short", i); 55862306a36Sopenharmony_ci return -ERANGE; 55962306a36Sopenharmony_ci } 56062306a36Sopenharmony_ci } 56162306a36Sopenharmony_ci 56262306a36Sopenharmony_ci for (other_port = 0; other_port < ds->num_ports; other_port++) { 56362306a36Sopenharmony_ci if (other_port == port) 56462306a36Sopenharmony_ci continue; 56562306a36Sopenharmony_ci 56662306a36Sopenharmony_ci if (sja1105_tas_check_conflicts(priv, other_port, admin)) 56762306a36Sopenharmony_ci return -ERANGE; 56862306a36Sopenharmony_ci } 56962306a36Sopenharmony_ci 57062306a36Sopenharmony_ci if (sja1105_gating_check_conflicts(priv, port, NULL)) { 57162306a36Sopenharmony_ci dev_err(ds->dev, "Conflict with tc-gate schedule\n"); 57262306a36Sopenharmony_ci return -ERANGE; 57362306a36Sopenharmony_ci } 57462306a36Sopenharmony_ci 57562306a36Sopenharmony_ci tas_data->offload[port] = taprio_offload_get(admin); 57662306a36Sopenharmony_ci 57762306a36Sopenharmony_ci rc = sja1105_init_scheduling(priv); 57862306a36Sopenharmony_ci if (rc < 0) 57962306a36Sopenharmony_ci return rc; 58062306a36Sopenharmony_ci 58162306a36Sopenharmony_ci return sja1105_static_config_reload(priv, SJA1105_SCHEDULING); 58262306a36Sopenharmony_ci} 58362306a36Sopenharmony_ci 58462306a36Sopenharmony_cistatic int sja1105_tas_check_running(struct sja1105_private *priv) 58562306a36Sopenharmony_ci{ 58662306a36Sopenharmony_ci struct sja1105_tas_data *tas_data = &priv->tas_data; 58762306a36Sopenharmony_ci struct dsa_switch *ds = priv->ds; 58862306a36Sopenharmony_ci struct sja1105_ptp_cmd cmd = {0}; 58962306a36Sopenharmony_ci int rc; 59062306a36Sopenharmony_ci 59162306a36Sopenharmony_ci rc = sja1105_ptp_commit(ds, &cmd, SPI_READ); 59262306a36Sopenharmony_ci if (rc < 0) 59362306a36Sopenharmony_ci return rc; 59462306a36Sopenharmony_ci 59562306a36Sopenharmony_ci if (cmd.ptpstrtsch == 1) 59662306a36Sopenharmony_ci /* Schedule successfully started */ 59762306a36Sopenharmony_ci tas_data->state = SJA1105_TAS_STATE_RUNNING; 59862306a36Sopenharmony_ci else if (cmd.ptpstopsch == 1) 59962306a36Sopenharmony_ci /* Schedule is stopped */ 60062306a36Sopenharmony_ci tas_data->state = SJA1105_TAS_STATE_DISABLED; 60162306a36Sopenharmony_ci else 60262306a36Sopenharmony_ci /* Schedule is probably not configured with PTP clock source */ 60362306a36Sopenharmony_ci rc = -EINVAL; 60462306a36Sopenharmony_ci 60562306a36Sopenharmony_ci return rc; 60662306a36Sopenharmony_ci} 60762306a36Sopenharmony_ci 60862306a36Sopenharmony_ci/* Write to PTPCLKCORP */ 60962306a36Sopenharmony_cistatic int sja1105_tas_adjust_drift(struct sja1105_private *priv, 61062306a36Sopenharmony_ci u64 correction) 61162306a36Sopenharmony_ci{ 61262306a36Sopenharmony_ci const struct sja1105_regs *regs = priv->info->regs; 61362306a36Sopenharmony_ci u32 ptpclkcorp = ns_to_sja1105_ticks(correction); 61462306a36Sopenharmony_ci 61562306a36Sopenharmony_ci return sja1105_xfer_u32(priv, SPI_WRITE, regs->ptpclkcorp, 61662306a36Sopenharmony_ci &ptpclkcorp, NULL); 61762306a36Sopenharmony_ci} 61862306a36Sopenharmony_ci 61962306a36Sopenharmony_ci/* Write to PTPSCHTM */ 62062306a36Sopenharmony_cistatic int sja1105_tas_set_base_time(struct sja1105_private *priv, 62162306a36Sopenharmony_ci u64 base_time) 62262306a36Sopenharmony_ci{ 62362306a36Sopenharmony_ci const struct sja1105_regs *regs = priv->info->regs; 62462306a36Sopenharmony_ci u64 ptpschtm = ns_to_sja1105_ticks(base_time); 62562306a36Sopenharmony_ci 62662306a36Sopenharmony_ci return sja1105_xfer_u64(priv, SPI_WRITE, regs->ptpschtm, 62762306a36Sopenharmony_ci &ptpschtm, NULL); 62862306a36Sopenharmony_ci} 62962306a36Sopenharmony_ci 63062306a36Sopenharmony_cistatic int sja1105_tas_start(struct sja1105_private *priv) 63162306a36Sopenharmony_ci{ 63262306a36Sopenharmony_ci struct sja1105_tas_data *tas_data = &priv->tas_data; 63362306a36Sopenharmony_ci struct sja1105_ptp_cmd *cmd = &priv->ptp_data.cmd; 63462306a36Sopenharmony_ci struct dsa_switch *ds = priv->ds; 63562306a36Sopenharmony_ci int rc; 63662306a36Sopenharmony_ci 63762306a36Sopenharmony_ci dev_dbg(ds->dev, "Starting the TAS\n"); 63862306a36Sopenharmony_ci 63962306a36Sopenharmony_ci if (tas_data->state == SJA1105_TAS_STATE_ENABLED_NOT_RUNNING || 64062306a36Sopenharmony_ci tas_data->state == SJA1105_TAS_STATE_RUNNING) { 64162306a36Sopenharmony_ci dev_err(ds->dev, "TAS already started\n"); 64262306a36Sopenharmony_ci return -EINVAL; 64362306a36Sopenharmony_ci } 64462306a36Sopenharmony_ci 64562306a36Sopenharmony_ci cmd->ptpstrtsch = 1; 64662306a36Sopenharmony_ci cmd->ptpstopsch = 0; 64762306a36Sopenharmony_ci 64862306a36Sopenharmony_ci rc = sja1105_ptp_commit(ds, cmd, SPI_WRITE); 64962306a36Sopenharmony_ci if (rc < 0) 65062306a36Sopenharmony_ci return rc; 65162306a36Sopenharmony_ci 65262306a36Sopenharmony_ci tas_data->state = SJA1105_TAS_STATE_ENABLED_NOT_RUNNING; 65362306a36Sopenharmony_ci 65462306a36Sopenharmony_ci return 0; 65562306a36Sopenharmony_ci} 65662306a36Sopenharmony_ci 65762306a36Sopenharmony_cistatic int sja1105_tas_stop(struct sja1105_private *priv) 65862306a36Sopenharmony_ci{ 65962306a36Sopenharmony_ci struct sja1105_tas_data *tas_data = &priv->tas_data; 66062306a36Sopenharmony_ci struct sja1105_ptp_cmd *cmd = &priv->ptp_data.cmd; 66162306a36Sopenharmony_ci struct dsa_switch *ds = priv->ds; 66262306a36Sopenharmony_ci int rc; 66362306a36Sopenharmony_ci 66462306a36Sopenharmony_ci dev_dbg(ds->dev, "Stopping the TAS\n"); 66562306a36Sopenharmony_ci 66662306a36Sopenharmony_ci if (tas_data->state == SJA1105_TAS_STATE_DISABLED) { 66762306a36Sopenharmony_ci dev_err(ds->dev, "TAS already disabled\n"); 66862306a36Sopenharmony_ci return -EINVAL; 66962306a36Sopenharmony_ci } 67062306a36Sopenharmony_ci 67162306a36Sopenharmony_ci cmd->ptpstopsch = 1; 67262306a36Sopenharmony_ci cmd->ptpstrtsch = 0; 67362306a36Sopenharmony_ci 67462306a36Sopenharmony_ci rc = sja1105_ptp_commit(ds, cmd, SPI_WRITE); 67562306a36Sopenharmony_ci if (rc < 0) 67662306a36Sopenharmony_ci return rc; 67762306a36Sopenharmony_ci 67862306a36Sopenharmony_ci tas_data->state = SJA1105_TAS_STATE_DISABLED; 67962306a36Sopenharmony_ci 68062306a36Sopenharmony_ci return 0; 68162306a36Sopenharmony_ci} 68262306a36Sopenharmony_ci 68362306a36Sopenharmony_ci/* The schedule engine and the PTP clock are driven by the same oscillator, and 68462306a36Sopenharmony_ci * they run in parallel. But whilst the PTP clock can keep an absolute 68562306a36Sopenharmony_ci * time-of-day, the schedule engine is only running in 'ticks' (25 ticks make 68662306a36Sopenharmony_ci * up a delta, which is 200ns), and wrapping around at the end of each cycle. 68762306a36Sopenharmony_ci * The schedule engine is started when the PTP clock reaches the PTPSCHTM time 68862306a36Sopenharmony_ci * (in PTP domain). 68962306a36Sopenharmony_ci * Because the PTP clock can be rate-corrected (accelerated or slowed down) by 69062306a36Sopenharmony_ci * a software servo, and the schedule engine clock runs in parallel to the PTP 69162306a36Sopenharmony_ci * clock, there is logic internal to the switch that periodically keeps the 69262306a36Sopenharmony_ci * schedule engine from drifting away. The frequency with which this internal 69362306a36Sopenharmony_ci * syntonization happens is the PTP clock correction period (PTPCLKCORP). It is 69462306a36Sopenharmony_ci * a value also in the PTP clock domain, and is also rate-corrected. 69562306a36Sopenharmony_ci * To be precise, during a correction period, there is logic to determine by 69662306a36Sopenharmony_ci * how many scheduler clock ticks has the PTP clock drifted. At the end of each 69762306a36Sopenharmony_ci * correction period/beginning of new one, the length of a delta is shrunk or 69862306a36Sopenharmony_ci * expanded with an integer number of ticks, compared with the typical 25. 69962306a36Sopenharmony_ci * So a delta lasts for 200ns (or 25 ticks) only on average. 70062306a36Sopenharmony_ci * Sometimes it is longer, sometimes it is shorter. The internal syntonization 70162306a36Sopenharmony_ci * logic can adjust for at most 5 ticks each 20 ticks. 70262306a36Sopenharmony_ci * 70362306a36Sopenharmony_ci * The first implication is that you should choose your schedule correction 70462306a36Sopenharmony_ci * period to be an integer multiple of the schedule length. Preferably one. 70562306a36Sopenharmony_ci * In case there are schedules of multiple ports active, then the correction 70662306a36Sopenharmony_ci * period needs to be a multiple of them all. Given the restriction that the 70762306a36Sopenharmony_ci * cycle times have to be multiples of one another anyway, this means the 70862306a36Sopenharmony_ci * correction period can simply be the largest cycle time, hence the current 70962306a36Sopenharmony_ci * choice. This way, the updates are always synchronous to the transmission 71062306a36Sopenharmony_ci * cycle, and therefore predictable. 71162306a36Sopenharmony_ci * 71262306a36Sopenharmony_ci * The second implication is that at the beginning of a correction period, the 71362306a36Sopenharmony_ci * first few deltas will be modulated in time, until the schedule engine is 71462306a36Sopenharmony_ci * properly phase-aligned with the PTP clock. For this reason, you should place 71562306a36Sopenharmony_ci * your best-effort traffic at the beginning of a cycle, and your 71662306a36Sopenharmony_ci * time-triggered traffic afterwards. 71762306a36Sopenharmony_ci * 71862306a36Sopenharmony_ci * The third implication is that once the schedule engine is started, it can 71962306a36Sopenharmony_ci * only adjust for so much drift within a correction period. In the servo you 72062306a36Sopenharmony_ci * can only change the PTPCLKRATE, but not step the clock (PTPCLKADD). If you 72162306a36Sopenharmony_ci * want to do the latter, you need to stop and restart the schedule engine, 72262306a36Sopenharmony_ci * which is what the state machine handles. 72362306a36Sopenharmony_ci */ 72462306a36Sopenharmony_cistatic void sja1105_tas_state_machine(struct work_struct *work) 72562306a36Sopenharmony_ci{ 72662306a36Sopenharmony_ci struct sja1105_tas_data *tas_data = work_to_sja1105_tas(work); 72762306a36Sopenharmony_ci struct sja1105_private *priv = tas_to_sja1105(tas_data); 72862306a36Sopenharmony_ci struct sja1105_ptp_data *ptp_data = &priv->ptp_data; 72962306a36Sopenharmony_ci struct timespec64 base_time_ts, now_ts; 73062306a36Sopenharmony_ci struct dsa_switch *ds = priv->ds; 73162306a36Sopenharmony_ci struct timespec64 diff; 73262306a36Sopenharmony_ci s64 base_time, now; 73362306a36Sopenharmony_ci int rc = 0; 73462306a36Sopenharmony_ci 73562306a36Sopenharmony_ci mutex_lock(&ptp_data->lock); 73662306a36Sopenharmony_ci 73762306a36Sopenharmony_ci switch (tas_data->state) { 73862306a36Sopenharmony_ci case SJA1105_TAS_STATE_DISABLED: 73962306a36Sopenharmony_ci /* Can't do anything at all if clock is still being stepped */ 74062306a36Sopenharmony_ci if (tas_data->last_op != SJA1105_PTP_ADJUSTFREQ) 74162306a36Sopenharmony_ci break; 74262306a36Sopenharmony_ci 74362306a36Sopenharmony_ci rc = sja1105_tas_adjust_drift(priv, tas_data->max_cycle_time); 74462306a36Sopenharmony_ci if (rc < 0) 74562306a36Sopenharmony_ci break; 74662306a36Sopenharmony_ci 74762306a36Sopenharmony_ci rc = __sja1105_ptp_gettimex(ds, &now, NULL); 74862306a36Sopenharmony_ci if (rc < 0) 74962306a36Sopenharmony_ci break; 75062306a36Sopenharmony_ci 75162306a36Sopenharmony_ci /* Plan to start the earliest schedule first. The others 75262306a36Sopenharmony_ci * will be started in hardware, by way of their respective 75362306a36Sopenharmony_ci * entry points delta. 75462306a36Sopenharmony_ci * Try our best to avoid fringe cases (race condition between 75562306a36Sopenharmony_ci * ptpschtm and ptpstrtsch) by pushing the oper_base_time at 75662306a36Sopenharmony_ci * least one second in the future from now. This is not ideal, 75762306a36Sopenharmony_ci * but this only needs to buy us time until the 75862306a36Sopenharmony_ci * sja1105_tas_start command below gets executed. 75962306a36Sopenharmony_ci */ 76062306a36Sopenharmony_ci base_time = future_base_time(tas_data->earliest_base_time, 76162306a36Sopenharmony_ci tas_data->max_cycle_time, 76262306a36Sopenharmony_ci now + 1ull * NSEC_PER_SEC); 76362306a36Sopenharmony_ci base_time -= sja1105_delta_to_ns(1); 76462306a36Sopenharmony_ci 76562306a36Sopenharmony_ci rc = sja1105_tas_set_base_time(priv, base_time); 76662306a36Sopenharmony_ci if (rc < 0) 76762306a36Sopenharmony_ci break; 76862306a36Sopenharmony_ci 76962306a36Sopenharmony_ci tas_data->oper_base_time = base_time; 77062306a36Sopenharmony_ci 77162306a36Sopenharmony_ci rc = sja1105_tas_start(priv); 77262306a36Sopenharmony_ci if (rc < 0) 77362306a36Sopenharmony_ci break; 77462306a36Sopenharmony_ci 77562306a36Sopenharmony_ci base_time_ts = ns_to_timespec64(base_time); 77662306a36Sopenharmony_ci now_ts = ns_to_timespec64(now); 77762306a36Sopenharmony_ci 77862306a36Sopenharmony_ci dev_dbg(ds->dev, "OPER base time %lld.%09ld (now %lld.%09ld)\n", 77962306a36Sopenharmony_ci base_time_ts.tv_sec, base_time_ts.tv_nsec, 78062306a36Sopenharmony_ci now_ts.tv_sec, now_ts.tv_nsec); 78162306a36Sopenharmony_ci 78262306a36Sopenharmony_ci break; 78362306a36Sopenharmony_ci 78462306a36Sopenharmony_ci case SJA1105_TAS_STATE_ENABLED_NOT_RUNNING: 78562306a36Sopenharmony_ci if (tas_data->last_op != SJA1105_PTP_ADJUSTFREQ) { 78662306a36Sopenharmony_ci /* Clock was stepped.. bad news for TAS */ 78762306a36Sopenharmony_ci sja1105_tas_stop(priv); 78862306a36Sopenharmony_ci break; 78962306a36Sopenharmony_ci } 79062306a36Sopenharmony_ci 79162306a36Sopenharmony_ci /* Check if TAS has actually started, by comparing the 79262306a36Sopenharmony_ci * scheduled start time with the SJA1105 PTP clock 79362306a36Sopenharmony_ci */ 79462306a36Sopenharmony_ci rc = __sja1105_ptp_gettimex(ds, &now, NULL); 79562306a36Sopenharmony_ci if (rc < 0) 79662306a36Sopenharmony_ci break; 79762306a36Sopenharmony_ci 79862306a36Sopenharmony_ci if (now < tas_data->oper_base_time) { 79962306a36Sopenharmony_ci /* TAS has not started yet */ 80062306a36Sopenharmony_ci diff = ns_to_timespec64(tas_data->oper_base_time - now); 80162306a36Sopenharmony_ci dev_dbg(ds->dev, "time to start: [%lld.%09ld]", 80262306a36Sopenharmony_ci diff.tv_sec, diff.tv_nsec); 80362306a36Sopenharmony_ci break; 80462306a36Sopenharmony_ci } 80562306a36Sopenharmony_ci 80662306a36Sopenharmony_ci /* Time elapsed, what happened? */ 80762306a36Sopenharmony_ci rc = sja1105_tas_check_running(priv); 80862306a36Sopenharmony_ci if (rc < 0) 80962306a36Sopenharmony_ci break; 81062306a36Sopenharmony_ci 81162306a36Sopenharmony_ci if (tas_data->state != SJA1105_TAS_STATE_RUNNING) 81262306a36Sopenharmony_ci /* TAS has started */ 81362306a36Sopenharmony_ci dev_err(ds->dev, 81462306a36Sopenharmony_ci "TAS not started despite time elapsed\n"); 81562306a36Sopenharmony_ci 81662306a36Sopenharmony_ci break; 81762306a36Sopenharmony_ci 81862306a36Sopenharmony_ci case SJA1105_TAS_STATE_RUNNING: 81962306a36Sopenharmony_ci /* Clock was stepped.. bad news for TAS */ 82062306a36Sopenharmony_ci if (tas_data->last_op != SJA1105_PTP_ADJUSTFREQ) { 82162306a36Sopenharmony_ci sja1105_tas_stop(priv); 82262306a36Sopenharmony_ci break; 82362306a36Sopenharmony_ci } 82462306a36Sopenharmony_ci 82562306a36Sopenharmony_ci rc = sja1105_tas_check_running(priv); 82662306a36Sopenharmony_ci if (rc < 0) 82762306a36Sopenharmony_ci break; 82862306a36Sopenharmony_ci 82962306a36Sopenharmony_ci if (tas_data->state != SJA1105_TAS_STATE_RUNNING) 83062306a36Sopenharmony_ci dev_err(ds->dev, "TAS surprisingly stopped\n"); 83162306a36Sopenharmony_ci 83262306a36Sopenharmony_ci break; 83362306a36Sopenharmony_ci 83462306a36Sopenharmony_ci default: 83562306a36Sopenharmony_ci if (net_ratelimit()) 83662306a36Sopenharmony_ci dev_err(ds->dev, "TAS in an invalid state (incorrect use of API)!\n"); 83762306a36Sopenharmony_ci } 83862306a36Sopenharmony_ci 83962306a36Sopenharmony_ci if (rc && net_ratelimit()) 84062306a36Sopenharmony_ci dev_err(ds->dev, "An operation returned %d\n", rc); 84162306a36Sopenharmony_ci 84262306a36Sopenharmony_ci mutex_unlock(&ptp_data->lock); 84362306a36Sopenharmony_ci} 84462306a36Sopenharmony_ci 84562306a36Sopenharmony_civoid sja1105_tas_clockstep(struct dsa_switch *ds) 84662306a36Sopenharmony_ci{ 84762306a36Sopenharmony_ci struct sja1105_private *priv = ds->priv; 84862306a36Sopenharmony_ci struct sja1105_tas_data *tas_data = &priv->tas_data; 84962306a36Sopenharmony_ci 85062306a36Sopenharmony_ci if (!tas_data->enabled) 85162306a36Sopenharmony_ci return; 85262306a36Sopenharmony_ci 85362306a36Sopenharmony_ci tas_data->last_op = SJA1105_PTP_CLOCKSTEP; 85462306a36Sopenharmony_ci schedule_work(&tas_data->tas_work); 85562306a36Sopenharmony_ci} 85662306a36Sopenharmony_ci 85762306a36Sopenharmony_civoid sja1105_tas_adjfreq(struct dsa_switch *ds) 85862306a36Sopenharmony_ci{ 85962306a36Sopenharmony_ci struct sja1105_private *priv = ds->priv; 86062306a36Sopenharmony_ci struct sja1105_tas_data *tas_data = &priv->tas_data; 86162306a36Sopenharmony_ci 86262306a36Sopenharmony_ci if (!tas_data->enabled) 86362306a36Sopenharmony_ci return; 86462306a36Sopenharmony_ci 86562306a36Sopenharmony_ci /* No reason to schedule the workqueue, nothing changed */ 86662306a36Sopenharmony_ci if (tas_data->state == SJA1105_TAS_STATE_RUNNING) 86762306a36Sopenharmony_ci return; 86862306a36Sopenharmony_ci 86962306a36Sopenharmony_ci tas_data->last_op = SJA1105_PTP_ADJUSTFREQ; 87062306a36Sopenharmony_ci schedule_work(&tas_data->tas_work); 87162306a36Sopenharmony_ci} 87262306a36Sopenharmony_ci 87362306a36Sopenharmony_civoid sja1105_tas_setup(struct dsa_switch *ds) 87462306a36Sopenharmony_ci{ 87562306a36Sopenharmony_ci struct sja1105_private *priv = ds->priv; 87662306a36Sopenharmony_ci struct sja1105_tas_data *tas_data = &priv->tas_data; 87762306a36Sopenharmony_ci 87862306a36Sopenharmony_ci INIT_WORK(&tas_data->tas_work, sja1105_tas_state_machine); 87962306a36Sopenharmony_ci tas_data->state = SJA1105_TAS_STATE_DISABLED; 88062306a36Sopenharmony_ci tas_data->last_op = SJA1105_PTP_NONE; 88162306a36Sopenharmony_ci 88262306a36Sopenharmony_ci INIT_LIST_HEAD(&tas_data->gating_cfg.entries); 88362306a36Sopenharmony_ci} 88462306a36Sopenharmony_ci 88562306a36Sopenharmony_civoid sja1105_tas_teardown(struct dsa_switch *ds) 88662306a36Sopenharmony_ci{ 88762306a36Sopenharmony_ci struct sja1105_private *priv = ds->priv; 88862306a36Sopenharmony_ci struct tc_taprio_qopt_offload *offload; 88962306a36Sopenharmony_ci int port; 89062306a36Sopenharmony_ci 89162306a36Sopenharmony_ci cancel_work_sync(&priv->tas_data.tas_work); 89262306a36Sopenharmony_ci 89362306a36Sopenharmony_ci for (port = 0; port < ds->num_ports; port++) { 89462306a36Sopenharmony_ci offload = priv->tas_data.offload[port]; 89562306a36Sopenharmony_ci if (!offload) 89662306a36Sopenharmony_ci continue; 89762306a36Sopenharmony_ci 89862306a36Sopenharmony_ci taprio_offload_free(offload); 89962306a36Sopenharmony_ci } 90062306a36Sopenharmony_ci} 901