162306a36Sopenharmony_ci// SPDX-License-Identifier: (GPL-2.0 OR MIT) 262306a36Sopenharmony_ci/* 362306a36Sopenharmony_ci * DSA driver for: 462306a36Sopenharmony_ci * Hirschmann Hellcreek TSN switch. 562306a36Sopenharmony_ci * 662306a36Sopenharmony_ci * Copyright (C) 2019,2020 Hochschule Offenburg 762306a36Sopenharmony_ci * Copyright (C) 2019,2020 Linutronix GmbH 862306a36Sopenharmony_ci * Authors: Kamil Alkhouri <kamil.alkhouri@hs-offenburg.de> 962306a36Sopenharmony_ci * Kurt Kanzenbach <kurt@linutronix.de> 1062306a36Sopenharmony_ci */ 1162306a36Sopenharmony_ci 1262306a36Sopenharmony_ci#include <linux/of.h> 1362306a36Sopenharmony_ci#include <linux/ptp_clock_kernel.h> 1462306a36Sopenharmony_ci#include "hellcreek.h" 1562306a36Sopenharmony_ci#include "hellcreek_ptp.h" 1662306a36Sopenharmony_ci#include "hellcreek_hwtstamp.h" 1762306a36Sopenharmony_ci 1862306a36Sopenharmony_ciu16 hellcreek_ptp_read(struct hellcreek *hellcreek, unsigned int offset) 1962306a36Sopenharmony_ci{ 2062306a36Sopenharmony_ci return readw(hellcreek->ptp_base + offset); 2162306a36Sopenharmony_ci} 2262306a36Sopenharmony_ci 2362306a36Sopenharmony_civoid hellcreek_ptp_write(struct hellcreek *hellcreek, u16 data, 2462306a36Sopenharmony_ci unsigned int offset) 2562306a36Sopenharmony_ci{ 2662306a36Sopenharmony_ci writew(data, hellcreek->ptp_base + offset); 2762306a36Sopenharmony_ci} 2862306a36Sopenharmony_ci 2962306a36Sopenharmony_ci/* Get nanoseconds from PTP clock */ 3062306a36Sopenharmony_cistatic u64 hellcreek_ptp_clock_read(struct hellcreek *hellcreek) 3162306a36Sopenharmony_ci{ 3262306a36Sopenharmony_ci u16 nsl, nsh; 3362306a36Sopenharmony_ci 3462306a36Sopenharmony_ci /* Take a snapshot */ 3562306a36Sopenharmony_ci hellcreek_ptp_write(hellcreek, PR_COMMAND_C_SS, PR_COMMAND_C); 3662306a36Sopenharmony_ci 3762306a36Sopenharmony_ci /* The time of the day is saved as 96 bits. However, due to hardware 3862306a36Sopenharmony_ci * limitations the seconds are not or only partly kept in the PTP 3962306a36Sopenharmony_ci * core. Currently only three bits for the seconds are available. That's 4062306a36Sopenharmony_ci * why only the nanoseconds are used and the seconds are tracked in 4162306a36Sopenharmony_ci * software. Anyway due to internal locking all five registers should be 4262306a36Sopenharmony_ci * read. 4362306a36Sopenharmony_ci */ 4462306a36Sopenharmony_ci nsh = hellcreek_ptp_read(hellcreek, PR_SS_SYNC_DATA_C); 4562306a36Sopenharmony_ci nsh = hellcreek_ptp_read(hellcreek, PR_SS_SYNC_DATA_C); 4662306a36Sopenharmony_ci nsh = hellcreek_ptp_read(hellcreek, PR_SS_SYNC_DATA_C); 4762306a36Sopenharmony_ci nsh = hellcreek_ptp_read(hellcreek, PR_SS_SYNC_DATA_C); 4862306a36Sopenharmony_ci nsl = hellcreek_ptp_read(hellcreek, PR_SS_SYNC_DATA_C); 4962306a36Sopenharmony_ci 5062306a36Sopenharmony_ci return (u64)nsl | ((u64)nsh << 16); 5162306a36Sopenharmony_ci} 5262306a36Sopenharmony_ci 5362306a36Sopenharmony_cistatic u64 __hellcreek_ptp_gettime(struct hellcreek *hellcreek) 5462306a36Sopenharmony_ci{ 5562306a36Sopenharmony_ci u64 ns; 5662306a36Sopenharmony_ci 5762306a36Sopenharmony_ci ns = hellcreek_ptp_clock_read(hellcreek); 5862306a36Sopenharmony_ci if (ns < hellcreek->last_ts) 5962306a36Sopenharmony_ci hellcreek->seconds++; 6062306a36Sopenharmony_ci hellcreek->last_ts = ns; 6162306a36Sopenharmony_ci ns += hellcreek->seconds * NSEC_PER_SEC; 6262306a36Sopenharmony_ci 6362306a36Sopenharmony_ci return ns; 6462306a36Sopenharmony_ci} 6562306a36Sopenharmony_ci 6662306a36Sopenharmony_ci/* Retrieve the seconds parts in nanoseconds for a packet timestamped with @ns. 6762306a36Sopenharmony_ci * There has to be a check whether an overflow occurred between the packet 6862306a36Sopenharmony_ci * arrival and now. If so use the correct seconds (-1) for calculating the 6962306a36Sopenharmony_ci * packet arrival time. 7062306a36Sopenharmony_ci */ 7162306a36Sopenharmony_ciu64 hellcreek_ptp_gettime_seconds(struct hellcreek *hellcreek, u64 ns) 7262306a36Sopenharmony_ci{ 7362306a36Sopenharmony_ci u64 s; 7462306a36Sopenharmony_ci 7562306a36Sopenharmony_ci __hellcreek_ptp_gettime(hellcreek); 7662306a36Sopenharmony_ci if (hellcreek->last_ts > ns) 7762306a36Sopenharmony_ci s = hellcreek->seconds * NSEC_PER_SEC; 7862306a36Sopenharmony_ci else 7962306a36Sopenharmony_ci s = (hellcreek->seconds - 1) * NSEC_PER_SEC; 8062306a36Sopenharmony_ci 8162306a36Sopenharmony_ci return s; 8262306a36Sopenharmony_ci} 8362306a36Sopenharmony_ci 8462306a36Sopenharmony_cistatic int hellcreek_ptp_gettime(struct ptp_clock_info *ptp, 8562306a36Sopenharmony_ci struct timespec64 *ts) 8662306a36Sopenharmony_ci{ 8762306a36Sopenharmony_ci struct hellcreek *hellcreek = ptp_to_hellcreek(ptp); 8862306a36Sopenharmony_ci u64 ns; 8962306a36Sopenharmony_ci 9062306a36Sopenharmony_ci mutex_lock(&hellcreek->ptp_lock); 9162306a36Sopenharmony_ci ns = __hellcreek_ptp_gettime(hellcreek); 9262306a36Sopenharmony_ci mutex_unlock(&hellcreek->ptp_lock); 9362306a36Sopenharmony_ci 9462306a36Sopenharmony_ci *ts = ns_to_timespec64(ns); 9562306a36Sopenharmony_ci 9662306a36Sopenharmony_ci return 0; 9762306a36Sopenharmony_ci} 9862306a36Sopenharmony_ci 9962306a36Sopenharmony_cistatic int hellcreek_ptp_settime(struct ptp_clock_info *ptp, 10062306a36Sopenharmony_ci const struct timespec64 *ts) 10162306a36Sopenharmony_ci{ 10262306a36Sopenharmony_ci struct hellcreek *hellcreek = ptp_to_hellcreek(ptp); 10362306a36Sopenharmony_ci u16 secl, nsh, nsl; 10462306a36Sopenharmony_ci 10562306a36Sopenharmony_ci secl = ts->tv_sec & 0xffff; 10662306a36Sopenharmony_ci nsh = ((u32)ts->tv_nsec & 0xffff0000) >> 16; 10762306a36Sopenharmony_ci nsl = ts->tv_nsec & 0xffff; 10862306a36Sopenharmony_ci 10962306a36Sopenharmony_ci mutex_lock(&hellcreek->ptp_lock); 11062306a36Sopenharmony_ci 11162306a36Sopenharmony_ci /* Update overflow data structure */ 11262306a36Sopenharmony_ci hellcreek->seconds = ts->tv_sec; 11362306a36Sopenharmony_ci hellcreek->last_ts = ts->tv_nsec; 11462306a36Sopenharmony_ci 11562306a36Sopenharmony_ci /* Set time in clock */ 11662306a36Sopenharmony_ci hellcreek_ptp_write(hellcreek, 0x00, PR_CLOCK_WRITE_C); 11762306a36Sopenharmony_ci hellcreek_ptp_write(hellcreek, 0x00, PR_CLOCK_WRITE_C); 11862306a36Sopenharmony_ci hellcreek_ptp_write(hellcreek, secl, PR_CLOCK_WRITE_C); 11962306a36Sopenharmony_ci hellcreek_ptp_write(hellcreek, nsh, PR_CLOCK_WRITE_C); 12062306a36Sopenharmony_ci hellcreek_ptp_write(hellcreek, nsl, PR_CLOCK_WRITE_C); 12162306a36Sopenharmony_ci 12262306a36Sopenharmony_ci mutex_unlock(&hellcreek->ptp_lock); 12362306a36Sopenharmony_ci 12462306a36Sopenharmony_ci return 0; 12562306a36Sopenharmony_ci} 12662306a36Sopenharmony_ci 12762306a36Sopenharmony_cistatic int hellcreek_ptp_adjfine(struct ptp_clock_info *ptp, long scaled_ppm) 12862306a36Sopenharmony_ci{ 12962306a36Sopenharmony_ci struct hellcreek *hellcreek = ptp_to_hellcreek(ptp); 13062306a36Sopenharmony_ci u16 negative = 0, addendh, addendl; 13162306a36Sopenharmony_ci u32 addend; 13262306a36Sopenharmony_ci u64 adj; 13362306a36Sopenharmony_ci 13462306a36Sopenharmony_ci if (scaled_ppm < 0) { 13562306a36Sopenharmony_ci negative = 1; 13662306a36Sopenharmony_ci scaled_ppm = -scaled_ppm; 13762306a36Sopenharmony_ci } 13862306a36Sopenharmony_ci 13962306a36Sopenharmony_ci /* IP-Core adjusts the nominal frequency by adding or subtracting 1 ns 14062306a36Sopenharmony_ci * from the 8 ns (period of the oscillator) every time the accumulator 14162306a36Sopenharmony_ci * register overflows. The value stored in the addend register is added 14262306a36Sopenharmony_ci * to the accumulator register every 8 ns. 14362306a36Sopenharmony_ci * 14462306a36Sopenharmony_ci * addend value = (2^30 * accumulator_overflow_rate) / 14562306a36Sopenharmony_ci * oscillator_frequency 14662306a36Sopenharmony_ci * where: 14762306a36Sopenharmony_ci * 14862306a36Sopenharmony_ci * oscillator_frequency = 125 MHz 14962306a36Sopenharmony_ci * accumulator_overflow_rate = 125 MHz * scaled_ppm * 2^-16 * 10^-6 * 8 15062306a36Sopenharmony_ci */ 15162306a36Sopenharmony_ci adj = scaled_ppm; 15262306a36Sopenharmony_ci adj <<= 11; 15362306a36Sopenharmony_ci addend = (u32)div_u64(adj, 15625); 15462306a36Sopenharmony_ci 15562306a36Sopenharmony_ci addendh = (addend & 0xffff0000) >> 16; 15662306a36Sopenharmony_ci addendl = addend & 0xffff; 15762306a36Sopenharmony_ci 15862306a36Sopenharmony_ci negative = (negative << 15) & 0x8000; 15962306a36Sopenharmony_ci 16062306a36Sopenharmony_ci mutex_lock(&hellcreek->ptp_lock); 16162306a36Sopenharmony_ci 16262306a36Sopenharmony_ci /* Set drift register */ 16362306a36Sopenharmony_ci hellcreek_ptp_write(hellcreek, negative, PR_CLOCK_DRIFT_C); 16462306a36Sopenharmony_ci hellcreek_ptp_write(hellcreek, 0x00, PR_CLOCK_DRIFT_C); 16562306a36Sopenharmony_ci hellcreek_ptp_write(hellcreek, 0x00, PR_CLOCK_DRIFT_C); 16662306a36Sopenharmony_ci hellcreek_ptp_write(hellcreek, addendh, PR_CLOCK_DRIFT_C); 16762306a36Sopenharmony_ci hellcreek_ptp_write(hellcreek, addendl, PR_CLOCK_DRIFT_C); 16862306a36Sopenharmony_ci 16962306a36Sopenharmony_ci mutex_unlock(&hellcreek->ptp_lock); 17062306a36Sopenharmony_ci 17162306a36Sopenharmony_ci return 0; 17262306a36Sopenharmony_ci} 17362306a36Sopenharmony_ci 17462306a36Sopenharmony_cistatic int hellcreek_ptp_adjtime(struct ptp_clock_info *ptp, s64 delta) 17562306a36Sopenharmony_ci{ 17662306a36Sopenharmony_ci struct hellcreek *hellcreek = ptp_to_hellcreek(ptp); 17762306a36Sopenharmony_ci u16 negative = 0, counth, countl; 17862306a36Sopenharmony_ci u32 count_val; 17962306a36Sopenharmony_ci 18062306a36Sopenharmony_ci /* If the offset is larger than IP-Core slow offset resources. Don't 18162306a36Sopenharmony_ci * consider slow adjustment. Rather, add the offset directly to the 18262306a36Sopenharmony_ci * current time 18362306a36Sopenharmony_ci */ 18462306a36Sopenharmony_ci if (abs(delta) > MAX_SLOW_OFFSET_ADJ) { 18562306a36Sopenharmony_ci struct timespec64 now, then = ns_to_timespec64(delta); 18662306a36Sopenharmony_ci 18762306a36Sopenharmony_ci hellcreek_ptp_gettime(ptp, &now); 18862306a36Sopenharmony_ci now = timespec64_add(now, then); 18962306a36Sopenharmony_ci hellcreek_ptp_settime(ptp, &now); 19062306a36Sopenharmony_ci 19162306a36Sopenharmony_ci return 0; 19262306a36Sopenharmony_ci } 19362306a36Sopenharmony_ci 19462306a36Sopenharmony_ci if (delta < 0) { 19562306a36Sopenharmony_ci negative = 1; 19662306a36Sopenharmony_ci delta = -delta; 19762306a36Sopenharmony_ci } 19862306a36Sopenharmony_ci 19962306a36Sopenharmony_ci /* 'count_val' does not exceed the maximum register size (2^30) */ 20062306a36Sopenharmony_ci count_val = div_s64(delta, MAX_NS_PER_STEP); 20162306a36Sopenharmony_ci 20262306a36Sopenharmony_ci counth = (count_val & 0xffff0000) >> 16; 20362306a36Sopenharmony_ci countl = count_val & 0xffff; 20462306a36Sopenharmony_ci 20562306a36Sopenharmony_ci negative = (negative << 15) & 0x8000; 20662306a36Sopenharmony_ci 20762306a36Sopenharmony_ci mutex_lock(&hellcreek->ptp_lock); 20862306a36Sopenharmony_ci 20962306a36Sopenharmony_ci /* Set offset write register */ 21062306a36Sopenharmony_ci hellcreek_ptp_write(hellcreek, negative, PR_CLOCK_OFFSET_C); 21162306a36Sopenharmony_ci hellcreek_ptp_write(hellcreek, MAX_NS_PER_STEP, PR_CLOCK_OFFSET_C); 21262306a36Sopenharmony_ci hellcreek_ptp_write(hellcreek, MIN_CLK_CYCLES_BETWEEN_STEPS, 21362306a36Sopenharmony_ci PR_CLOCK_OFFSET_C); 21462306a36Sopenharmony_ci hellcreek_ptp_write(hellcreek, countl, PR_CLOCK_OFFSET_C); 21562306a36Sopenharmony_ci hellcreek_ptp_write(hellcreek, counth, PR_CLOCK_OFFSET_C); 21662306a36Sopenharmony_ci 21762306a36Sopenharmony_ci mutex_unlock(&hellcreek->ptp_lock); 21862306a36Sopenharmony_ci 21962306a36Sopenharmony_ci return 0; 22062306a36Sopenharmony_ci} 22162306a36Sopenharmony_ci 22262306a36Sopenharmony_cistatic int hellcreek_ptp_enable(struct ptp_clock_info *ptp, 22362306a36Sopenharmony_ci struct ptp_clock_request *rq, int on) 22462306a36Sopenharmony_ci{ 22562306a36Sopenharmony_ci return -EOPNOTSUPP; 22662306a36Sopenharmony_ci} 22762306a36Sopenharmony_ci 22862306a36Sopenharmony_cistatic void hellcreek_ptp_overflow_check(struct work_struct *work) 22962306a36Sopenharmony_ci{ 23062306a36Sopenharmony_ci struct delayed_work *dw = to_delayed_work(work); 23162306a36Sopenharmony_ci struct hellcreek *hellcreek; 23262306a36Sopenharmony_ci 23362306a36Sopenharmony_ci hellcreek = dw_overflow_to_hellcreek(dw); 23462306a36Sopenharmony_ci 23562306a36Sopenharmony_ci mutex_lock(&hellcreek->ptp_lock); 23662306a36Sopenharmony_ci __hellcreek_ptp_gettime(hellcreek); 23762306a36Sopenharmony_ci mutex_unlock(&hellcreek->ptp_lock); 23862306a36Sopenharmony_ci 23962306a36Sopenharmony_ci schedule_delayed_work(&hellcreek->overflow_work, 24062306a36Sopenharmony_ci HELLCREEK_OVERFLOW_PERIOD); 24162306a36Sopenharmony_ci} 24262306a36Sopenharmony_ci 24362306a36Sopenharmony_cistatic enum led_brightness hellcreek_get_brightness(struct hellcreek *hellcreek, 24462306a36Sopenharmony_ci int led) 24562306a36Sopenharmony_ci{ 24662306a36Sopenharmony_ci return (hellcreek->status_out & led) ? 1 : 0; 24762306a36Sopenharmony_ci} 24862306a36Sopenharmony_ci 24962306a36Sopenharmony_cistatic void hellcreek_set_brightness(struct hellcreek *hellcreek, int led, 25062306a36Sopenharmony_ci enum led_brightness b) 25162306a36Sopenharmony_ci{ 25262306a36Sopenharmony_ci mutex_lock(&hellcreek->ptp_lock); 25362306a36Sopenharmony_ci 25462306a36Sopenharmony_ci if (b) 25562306a36Sopenharmony_ci hellcreek->status_out |= led; 25662306a36Sopenharmony_ci else 25762306a36Sopenharmony_ci hellcreek->status_out &= ~led; 25862306a36Sopenharmony_ci 25962306a36Sopenharmony_ci hellcreek_ptp_write(hellcreek, hellcreek->status_out, STATUS_OUT); 26062306a36Sopenharmony_ci 26162306a36Sopenharmony_ci mutex_unlock(&hellcreek->ptp_lock); 26262306a36Sopenharmony_ci} 26362306a36Sopenharmony_ci 26462306a36Sopenharmony_cistatic void hellcreek_led_sync_good_set(struct led_classdev *ldev, 26562306a36Sopenharmony_ci enum led_brightness b) 26662306a36Sopenharmony_ci{ 26762306a36Sopenharmony_ci struct hellcreek *hellcreek = led_to_hellcreek(ldev, led_sync_good); 26862306a36Sopenharmony_ci 26962306a36Sopenharmony_ci hellcreek_set_brightness(hellcreek, STATUS_OUT_SYNC_GOOD, b); 27062306a36Sopenharmony_ci} 27162306a36Sopenharmony_ci 27262306a36Sopenharmony_cistatic enum led_brightness hellcreek_led_sync_good_get(struct led_classdev *ldev) 27362306a36Sopenharmony_ci{ 27462306a36Sopenharmony_ci struct hellcreek *hellcreek = led_to_hellcreek(ldev, led_sync_good); 27562306a36Sopenharmony_ci 27662306a36Sopenharmony_ci return hellcreek_get_brightness(hellcreek, STATUS_OUT_SYNC_GOOD); 27762306a36Sopenharmony_ci} 27862306a36Sopenharmony_ci 27962306a36Sopenharmony_cistatic void hellcreek_led_is_gm_set(struct led_classdev *ldev, 28062306a36Sopenharmony_ci enum led_brightness b) 28162306a36Sopenharmony_ci{ 28262306a36Sopenharmony_ci struct hellcreek *hellcreek = led_to_hellcreek(ldev, led_is_gm); 28362306a36Sopenharmony_ci 28462306a36Sopenharmony_ci hellcreek_set_brightness(hellcreek, STATUS_OUT_IS_GM, b); 28562306a36Sopenharmony_ci} 28662306a36Sopenharmony_ci 28762306a36Sopenharmony_cistatic enum led_brightness hellcreek_led_is_gm_get(struct led_classdev *ldev) 28862306a36Sopenharmony_ci{ 28962306a36Sopenharmony_ci struct hellcreek *hellcreek = led_to_hellcreek(ldev, led_is_gm); 29062306a36Sopenharmony_ci 29162306a36Sopenharmony_ci return hellcreek_get_brightness(hellcreek, STATUS_OUT_IS_GM); 29262306a36Sopenharmony_ci} 29362306a36Sopenharmony_ci 29462306a36Sopenharmony_ci/* There two available LEDs internally called sync_good and is_gm. However, the 29562306a36Sopenharmony_ci * user might want to use a different label and specify the default state. Take 29662306a36Sopenharmony_ci * those properties from device tree. 29762306a36Sopenharmony_ci */ 29862306a36Sopenharmony_cistatic int hellcreek_led_setup(struct hellcreek *hellcreek) 29962306a36Sopenharmony_ci{ 30062306a36Sopenharmony_ci struct device_node *leds, *led = NULL; 30162306a36Sopenharmony_ci enum led_default_state state; 30262306a36Sopenharmony_ci const char *label; 30362306a36Sopenharmony_ci int ret = -EINVAL; 30462306a36Sopenharmony_ci 30562306a36Sopenharmony_ci of_node_get(hellcreek->dev->of_node); 30662306a36Sopenharmony_ci leds = of_find_node_by_name(hellcreek->dev->of_node, "leds"); 30762306a36Sopenharmony_ci if (!leds) { 30862306a36Sopenharmony_ci dev_err(hellcreek->dev, "No LEDs specified in device tree!\n"); 30962306a36Sopenharmony_ci return ret; 31062306a36Sopenharmony_ci } 31162306a36Sopenharmony_ci 31262306a36Sopenharmony_ci hellcreek->status_out = 0; 31362306a36Sopenharmony_ci 31462306a36Sopenharmony_ci led = of_get_next_available_child(leds, led); 31562306a36Sopenharmony_ci if (!led) { 31662306a36Sopenharmony_ci dev_err(hellcreek->dev, "First LED not specified!\n"); 31762306a36Sopenharmony_ci goto out; 31862306a36Sopenharmony_ci } 31962306a36Sopenharmony_ci 32062306a36Sopenharmony_ci ret = of_property_read_string(led, "label", &label); 32162306a36Sopenharmony_ci hellcreek->led_sync_good.name = ret ? "sync_good" : label; 32262306a36Sopenharmony_ci 32362306a36Sopenharmony_ci state = led_init_default_state_get(of_fwnode_handle(led)); 32462306a36Sopenharmony_ci switch (state) { 32562306a36Sopenharmony_ci case LEDS_DEFSTATE_ON: 32662306a36Sopenharmony_ci hellcreek->led_sync_good.brightness = 1; 32762306a36Sopenharmony_ci break; 32862306a36Sopenharmony_ci case LEDS_DEFSTATE_KEEP: 32962306a36Sopenharmony_ci hellcreek->led_sync_good.brightness = 33062306a36Sopenharmony_ci hellcreek_get_brightness(hellcreek, STATUS_OUT_SYNC_GOOD); 33162306a36Sopenharmony_ci break; 33262306a36Sopenharmony_ci default: 33362306a36Sopenharmony_ci hellcreek->led_sync_good.brightness = 0; 33462306a36Sopenharmony_ci } 33562306a36Sopenharmony_ci 33662306a36Sopenharmony_ci hellcreek->led_sync_good.max_brightness = 1; 33762306a36Sopenharmony_ci hellcreek->led_sync_good.brightness_set = hellcreek_led_sync_good_set; 33862306a36Sopenharmony_ci hellcreek->led_sync_good.brightness_get = hellcreek_led_sync_good_get; 33962306a36Sopenharmony_ci 34062306a36Sopenharmony_ci led = of_get_next_available_child(leds, led); 34162306a36Sopenharmony_ci if (!led) { 34262306a36Sopenharmony_ci dev_err(hellcreek->dev, "Second LED not specified!\n"); 34362306a36Sopenharmony_ci ret = -EINVAL; 34462306a36Sopenharmony_ci goto out; 34562306a36Sopenharmony_ci } 34662306a36Sopenharmony_ci 34762306a36Sopenharmony_ci ret = of_property_read_string(led, "label", &label); 34862306a36Sopenharmony_ci hellcreek->led_is_gm.name = ret ? "is_gm" : label; 34962306a36Sopenharmony_ci 35062306a36Sopenharmony_ci state = led_init_default_state_get(of_fwnode_handle(led)); 35162306a36Sopenharmony_ci switch (state) { 35262306a36Sopenharmony_ci case LEDS_DEFSTATE_ON: 35362306a36Sopenharmony_ci hellcreek->led_is_gm.brightness = 1; 35462306a36Sopenharmony_ci break; 35562306a36Sopenharmony_ci case LEDS_DEFSTATE_KEEP: 35662306a36Sopenharmony_ci hellcreek->led_is_gm.brightness = 35762306a36Sopenharmony_ci hellcreek_get_brightness(hellcreek, STATUS_OUT_IS_GM); 35862306a36Sopenharmony_ci break; 35962306a36Sopenharmony_ci default: 36062306a36Sopenharmony_ci hellcreek->led_is_gm.brightness = 0; 36162306a36Sopenharmony_ci } 36262306a36Sopenharmony_ci 36362306a36Sopenharmony_ci hellcreek->led_is_gm.max_brightness = 1; 36462306a36Sopenharmony_ci hellcreek->led_is_gm.brightness_set = hellcreek_led_is_gm_set; 36562306a36Sopenharmony_ci hellcreek->led_is_gm.brightness_get = hellcreek_led_is_gm_get; 36662306a36Sopenharmony_ci 36762306a36Sopenharmony_ci /* Set initial state */ 36862306a36Sopenharmony_ci if (hellcreek->led_sync_good.brightness == 1) 36962306a36Sopenharmony_ci hellcreek_set_brightness(hellcreek, STATUS_OUT_SYNC_GOOD, 1); 37062306a36Sopenharmony_ci if (hellcreek->led_is_gm.brightness == 1) 37162306a36Sopenharmony_ci hellcreek_set_brightness(hellcreek, STATUS_OUT_IS_GM, 1); 37262306a36Sopenharmony_ci 37362306a36Sopenharmony_ci /* Register both leds */ 37462306a36Sopenharmony_ci led_classdev_register(hellcreek->dev, &hellcreek->led_sync_good); 37562306a36Sopenharmony_ci led_classdev_register(hellcreek->dev, &hellcreek->led_is_gm); 37662306a36Sopenharmony_ci 37762306a36Sopenharmony_ci ret = 0; 37862306a36Sopenharmony_ci 37962306a36Sopenharmony_ciout: 38062306a36Sopenharmony_ci of_node_put(leds); 38162306a36Sopenharmony_ci 38262306a36Sopenharmony_ci return ret; 38362306a36Sopenharmony_ci} 38462306a36Sopenharmony_ci 38562306a36Sopenharmony_ciint hellcreek_ptp_setup(struct hellcreek *hellcreek) 38662306a36Sopenharmony_ci{ 38762306a36Sopenharmony_ci u16 status; 38862306a36Sopenharmony_ci int ret; 38962306a36Sopenharmony_ci 39062306a36Sopenharmony_ci /* Set up the overflow work */ 39162306a36Sopenharmony_ci INIT_DELAYED_WORK(&hellcreek->overflow_work, 39262306a36Sopenharmony_ci hellcreek_ptp_overflow_check); 39362306a36Sopenharmony_ci 39462306a36Sopenharmony_ci /* Setup PTP clock */ 39562306a36Sopenharmony_ci hellcreek->ptp_clock_info.owner = THIS_MODULE; 39662306a36Sopenharmony_ci snprintf(hellcreek->ptp_clock_info.name, 39762306a36Sopenharmony_ci sizeof(hellcreek->ptp_clock_info.name), 39862306a36Sopenharmony_ci dev_name(hellcreek->dev)); 39962306a36Sopenharmony_ci 40062306a36Sopenharmony_ci /* IP-Core can add up to 0.5 ns per 8 ns cycle, which means 40162306a36Sopenharmony_ci * accumulator_overflow_rate shall not exceed 62.5 MHz (which adjusts 40262306a36Sopenharmony_ci * the nominal frequency by 6.25%) 40362306a36Sopenharmony_ci */ 40462306a36Sopenharmony_ci hellcreek->ptp_clock_info.max_adj = 62500000; 40562306a36Sopenharmony_ci hellcreek->ptp_clock_info.n_alarm = 0; 40662306a36Sopenharmony_ci hellcreek->ptp_clock_info.n_pins = 0; 40762306a36Sopenharmony_ci hellcreek->ptp_clock_info.n_ext_ts = 0; 40862306a36Sopenharmony_ci hellcreek->ptp_clock_info.n_per_out = 0; 40962306a36Sopenharmony_ci hellcreek->ptp_clock_info.pps = 0; 41062306a36Sopenharmony_ci hellcreek->ptp_clock_info.adjfine = hellcreek_ptp_adjfine; 41162306a36Sopenharmony_ci hellcreek->ptp_clock_info.adjtime = hellcreek_ptp_adjtime; 41262306a36Sopenharmony_ci hellcreek->ptp_clock_info.gettime64 = hellcreek_ptp_gettime; 41362306a36Sopenharmony_ci hellcreek->ptp_clock_info.settime64 = hellcreek_ptp_settime; 41462306a36Sopenharmony_ci hellcreek->ptp_clock_info.enable = hellcreek_ptp_enable; 41562306a36Sopenharmony_ci hellcreek->ptp_clock_info.do_aux_work = hellcreek_hwtstamp_work; 41662306a36Sopenharmony_ci 41762306a36Sopenharmony_ci hellcreek->ptp_clock = ptp_clock_register(&hellcreek->ptp_clock_info, 41862306a36Sopenharmony_ci hellcreek->dev); 41962306a36Sopenharmony_ci if (IS_ERR(hellcreek->ptp_clock)) 42062306a36Sopenharmony_ci return PTR_ERR(hellcreek->ptp_clock); 42162306a36Sopenharmony_ci 42262306a36Sopenharmony_ci /* Enable the offset correction process, if no offset correction is 42362306a36Sopenharmony_ci * already taking place 42462306a36Sopenharmony_ci */ 42562306a36Sopenharmony_ci status = hellcreek_ptp_read(hellcreek, PR_CLOCK_STATUS_C); 42662306a36Sopenharmony_ci if (!(status & PR_CLOCK_STATUS_C_OFS_ACT)) 42762306a36Sopenharmony_ci hellcreek_ptp_write(hellcreek, 42862306a36Sopenharmony_ci status | PR_CLOCK_STATUS_C_ENA_OFS, 42962306a36Sopenharmony_ci PR_CLOCK_STATUS_C); 43062306a36Sopenharmony_ci 43162306a36Sopenharmony_ci /* Enable the drift correction process */ 43262306a36Sopenharmony_ci hellcreek_ptp_write(hellcreek, status | PR_CLOCK_STATUS_C_ENA_DRIFT, 43362306a36Sopenharmony_ci PR_CLOCK_STATUS_C); 43462306a36Sopenharmony_ci 43562306a36Sopenharmony_ci /* LED setup */ 43662306a36Sopenharmony_ci ret = hellcreek_led_setup(hellcreek); 43762306a36Sopenharmony_ci if (ret) { 43862306a36Sopenharmony_ci if (hellcreek->ptp_clock) 43962306a36Sopenharmony_ci ptp_clock_unregister(hellcreek->ptp_clock); 44062306a36Sopenharmony_ci return ret; 44162306a36Sopenharmony_ci } 44262306a36Sopenharmony_ci 44362306a36Sopenharmony_ci schedule_delayed_work(&hellcreek->overflow_work, 44462306a36Sopenharmony_ci HELLCREEK_OVERFLOW_PERIOD); 44562306a36Sopenharmony_ci 44662306a36Sopenharmony_ci return 0; 44762306a36Sopenharmony_ci} 44862306a36Sopenharmony_ci 44962306a36Sopenharmony_civoid hellcreek_ptp_free(struct hellcreek *hellcreek) 45062306a36Sopenharmony_ci{ 45162306a36Sopenharmony_ci led_classdev_unregister(&hellcreek->led_is_gm); 45262306a36Sopenharmony_ci led_classdev_unregister(&hellcreek->led_sync_good); 45362306a36Sopenharmony_ci cancel_delayed_work_sync(&hellcreek->overflow_work); 45462306a36Sopenharmony_ci if (hellcreek->ptp_clock) 45562306a36Sopenharmony_ci ptp_clock_unregister(hellcreek->ptp_clock); 45662306a36Sopenharmony_ci hellcreek->ptp_clock = NULL; 45762306a36Sopenharmony_ci} 458