0001
0002
0003
0004
0005
0006
0007
0008
0009
0010
0011
0012 #include <linux/ptp_clock_kernel.h>
0013 #include "hellcreek.h"
0014 #include "hellcreek_ptp.h"
0015 #include "hellcreek_hwtstamp.h"
0016
0017 u16 hellcreek_ptp_read(struct hellcreek *hellcreek, unsigned int offset)
0018 {
0019 return readw(hellcreek->ptp_base + offset);
0020 }
0021
0022 void hellcreek_ptp_write(struct hellcreek *hellcreek, u16 data,
0023 unsigned int offset)
0024 {
0025 writew(data, hellcreek->ptp_base + offset);
0026 }
0027
0028
0029 static u64 hellcreek_ptp_clock_read(struct hellcreek *hellcreek)
0030 {
0031 u16 nsl, nsh;
0032
0033
0034 hellcreek_ptp_write(hellcreek, PR_COMMAND_C_SS, PR_COMMAND_C);
0035
0036
0037
0038
0039
0040
0041
0042
0043 nsh = hellcreek_ptp_read(hellcreek, PR_SS_SYNC_DATA_C);
0044 nsh = hellcreek_ptp_read(hellcreek, PR_SS_SYNC_DATA_C);
0045 nsh = hellcreek_ptp_read(hellcreek, PR_SS_SYNC_DATA_C);
0046 nsh = hellcreek_ptp_read(hellcreek, PR_SS_SYNC_DATA_C);
0047 nsl = hellcreek_ptp_read(hellcreek, PR_SS_SYNC_DATA_C);
0048
0049 return (u64)nsl | ((u64)nsh << 16);
0050 }
0051
0052 static u64 __hellcreek_ptp_gettime(struct hellcreek *hellcreek)
0053 {
0054 u64 ns;
0055
0056 ns = hellcreek_ptp_clock_read(hellcreek);
0057 if (ns < hellcreek->last_ts)
0058 hellcreek->seconds++;
0059 hellcreek->last_ts = ns;
0060 ns += hellcreek->seconds * NSEC_PER_SEC;
0061
0062 return ns;
0063 }
0064
0065
0066
0067
0068
0069
0070 u64 hellcreek_ptp_gettime_seconds(struct hellcreek *hellcreek, u64 ns)
0071 {
0072 u64 s;
0073
0074 __hellcreek_ptp_gettime(hellcreek);
0075 if (hellcreek->last_ts > ns)
0076 s = hellcreek->seconds * NSEC_PER_SEC;
0077 else
0078 s = (hellcreek->seconds - 1) * NSEC_PER_SEC;
0079
0080 return s;
0081 }
0082
0083 static int hellcreek_ptp_gettime(struct ptp_clock_info *ptp,
0084 struct timespec64 *ts)
0085 {
0086 struct hellcreek *hellcreek = ptp_to_hellcreek(ptp);
0087 u64 ns;
0088
0089 mutex_lock(&hellcreek->ptp_lock);
0090 ns = __hellcreek_ptp_gettime(hellcreek);
0091 mutex_unlock(&hellcreek->ptp_lock);
0092
0093 *ts = ns_to_timespec64(ns);
0094
0095 return 0;
0096 }
0097
0098 static int hellcreek_ptp_settime(struct ptp_clock_info *ptp,
0099 const struct timespec64 *ts)
0100 {
0101 struct hellcreek *hellcreek = ptp_to_hellcreek(ptp);
0102 u16 secl, nsh, nsl;
0103
0104 secl = ts->tv_sec & 0xffff;
0105 nsh = ((u32)ts->tv_nsec & 0xffff0000) >> 16;
0106 nsl = ts->tv_nsec & 0xffff;
0107
0108 mutex_lock(&hellcreek->ptp_lock);
0109
0110
0111 hellcreek->seconds = ts->tv_sec;
0112 hellcreek->last_ts = ts->tv_nsec;
0113
0114
0115 hellcreek_ptp_write(hellcreek, 0x00, PR_CLOCK_WRITE_C);
0116 hellcreek_ptp_write(hellcreek, 0x00, PR_CLOCK_WRITE_C);
0117 hellcreek_ptp_write(hellcreek, secl, PR_CLOCK_WRITE_C);
0118 hellcreek_ptp_write(hellcreek, nsh, PR_CLOCK_WRITE_C);
0119 hellcreek_ptp_write(hellcreek, nsl, PR_CLOCK_WRITE_C);
0120
0121 mutex_unlock(&hellcreek->ptp_lock);
0122
0123 return 0;
0124 }
0125
0126 static int hellcreek_ptp_adjfine(struct ptp_clock_info *ptp, long scaled_ppm)
0127 {
0128 struct hellcreek *hellcreek = ptp_to_hellcreek(ptp);
0129 u16 negative = 0, addendh, addendl;
0130 u32 addend;
0131 u64 adj;
0132
0133 if (scaled_ppm < 0) {
0134 negative = 1;
0135 scaled_ppm = -scaled_ppm;
0136 }
0137
0138
0139
0140
0141
0142
0143
0144
0145
0146
0147
0148
0149
0150 adj = scaled_ppm;
0151 adj <<= 11;
0152 addend = (u32)div_u64(adj, 15625);
0153
0154 addendh = (addend & 0xffff0000) >> 16;
0155 addendl = addend & 0xffff;
0156
0157 negative = (negative << 15) & 0x8000;
0158
0159 mutex_lock(&hellcreek->ptp_lock);
0160
0161
0162 hellcreek_ptp_write(hellcreek, negative, PR_CLOCK_DRIFT_C);
0163 hellcreek_ptp_write(hellcreek, 0x00, PR_CLOCK_DRIFT_C);
0164 hellcreek_ptp_write(hellcreek, 0x00, PR_CLOCK_DRIFT_C);
0165 hellcreek_ptp_write(hellcreek, addendh, PR_CLOCK_DRIFT_C);
0166 hellcreek_ptp_write(hellcreek, addendl, PR_CLOCK_DRIFT_C);
0167
0168 mutex_unlock(&hellcreek->ptp_lock);
0169
0170 return 0;
0171 }
0172
0173 static int hellcreek_ptp_adjtime(struct ptp_clock_info *ptp, s64 delta)
0174 {
0175 struct hellcreek *hellcreek = ptp_to_hellcreek(ptp);
0176 u16 negative = 0, counth, countl;
0177 u32 count_val;
0178
0179
0180
0181
0182
0183 if (abs(delta) > MAX_SLOW_OFFSET_ADJ) {
0184 struct timespec64 now, then = ns_to_timespec64(delta);
0185
0186 hellcreek_ptp_gettime(ptp, &now);
0187 now = timespec64_add(now, then);
0188 hellcreek_ptp_settime(ptp, &now);
0189
0190 return 0;
0191 }
0192
0193 if (delta < 0) {
0194 negative = 1;
0195 delta = -delta;
0196 }
0197
0198
0199 count_val = div_s64(delta, MAX_NS_PER_STEP);
0200
0201 counth = (count_val & 0xffff0000) >> 16;
0202 countl = count_val & 0xffff;
0203
0204 negative = (negative << 15) & 0x8000;
0205
0206 mutex_lock(&hellcreek->ptp_lock);
0207
0208
0209 hellcreek_ptp_write(hellcreek, negative, PR_CLOCK_OFFSET_C);
0210 hellcreek_ptp_write(hellcreek, MAX_NS_PER_STEP, PR_CLOCK_OFFSET_C);
0211 hellcreek_ptp_write(hellcreek, MIN_CLK_CYCLES_BETWEEN_STEPS,
0212 PR_CLOCK_OFFSET_C);
0213 hellcreek_ptp_write(hellcreek, countl, PR_CLOCK_OFFSET_C);
0214 hellcreek_ptp_write(hellcreek, counth, PR_CLOCK_OFFSET_C);
0215
0216 mutex_unlock(&hellcreek->ptp_lock);
0217
0218 return 0;
0219 }
0220
0221 static int hellcreek_ptp_enable(struct ptp_clock_info *ptp,
0222 struct ptp_clock_request *rq, int on)
0223 {
0224 return -EOPNOTSUPP;
0225 }
0226
0227 static void hellcreek_ptp_overflow_check(struct work_struct *work)
0228 {
0229 struct delayed_work *dw = to_delayed_work(work);
0230 struct hellcreek *hellcreek;
0231
0232 hellcreek = dw_overflow_to_hellcreek(dw);
0233
0234 mutex_lock(&hellcreek->ptp_lock);
0235 __hellcreek_ptp_gettime(hellcreek);
0236 mutex_unlock(&hellcreek->ptp_lock);
0237
0238 schedule_delayed_work(&hellcreek->overflow_work,
0239 HELLCREEK_OVERFLOW_PERIOD);
0240 }
0241
0242 static enum led_brightness hellcreek_get_brightness(struct hellcreek *hellcreek,
0243 int led)
0244 {
0245 return (hellcreek->status_out & led) ? 1 : 0;
0246 }
0247
0248 static void hellcreek_set_brightness(struct hellcreek *hellcreek, int led,
0249 enum led_brightness b)
0250 {
0251 mutex_lock(&hellcreek->ptp_lock);
0252
0253 if (b)
0254 hellcreek->status_out |= led;
0255 else
0256 hellcreek->status_out &= ~led;
0257
0258 hellcreek_ptp_write(hellcreek, hellcreek->status_out, STATUS_OUT);
0259
0260 mutex_unlock(&hellcreek->ptp_lock);
0261 }
0262
0263 static void hellcreek_led_sync_good_set(struct led_classdev *ldev,
0264 enum led_brightness b)
0265 {
0266 struct hellcreek *hellcreek = led_to_hellcreek(ldev, led_sync_good);
0267
0268 hellcreek_set_brightness(hellcreek, STATUS_OUT_SYNC_GOOD, b);
0269 }
0270
0271 static enum led_brightness hellcreek_led_sync_good_get(struct led_classdev *ldev)
0272 {
0273 struct hellcreek *hellcreek = led_to_hellcreek(ldev, led_sync_good);
0274
0275 return hellcreek_get_brightness(hellcreek, STATUS_OUT_SYNC_GOOD);
0276 }
0277
0278 static void hellcreek_led_is_gm_set(struct led_classdev *ldev,
0279 enum led_brightness b)
0280 {
0281 struct hellcreek *hellcreek = led_to_hellcreek(ldev, led_is_gm);
0282
0283 hellcreek_set_brightness(hellcreek, STATUS_OUT_IS_GM, b);
0284 }
0285
0286 static enum led_brightness hellcreek_led_is_gm_get(struct led_classdev *ldev)
0287 {
0288 struct hellcreek *hellcreek = led_to_hellcreek(ldev, led_is_gm);
0289
0290 return hellcreek_get_brightness(hellcreek, STATUS_OUT_IS_GM);
0291 }
0292
0293
0294
0295
0296
0297 static int hellcreek_led_setup(struct hellcreek *hellcreek)
0298 {
0299 struct device_node *leds, *led = NULL;
0300 const char *label, *state;
0301 int ret = -EINVAL;
0302
0303 of_node_get(hellcreek->dev->of_node);
0304 leds = of_find_node_by_name(hellcreek->dev->of_node, "leds");
0305 if (!leds) {
0306 dev_err(hellcreek->dev, "No LEDs specified in device tree!\n");
0307 return ret;
0308 }
0309
0310 hellcreek->status_out = 0;
0311
0312 led = of_get_next_available_child(leds, led);
0313 if (!led) {
0314 dev_err(hellcreek->dev, "First LED not specified!\n");
0315 goto out;
0316 }
0317
0318 ret = of_property_read_string(led, "label", &label);
0319 hellcreek->led_sync_good.name = ret ? "sync_good" : label;
0320
0321 ret = of_property_read_string(led, "default-state", &state);
0322 if (!ret) {
0323 if (!strcmp(state, "on"))
0324 hellcreek->led_sync_good.brightness = 1;
0325 else if (!strcmp(state, "off"))
0326 hellcreek->led_sync_good.brightness = 0;
0327 else if (!strcmp(state, "keep"))
0328 hellcreek->led_sync_good.brightness =
0329 hellcreek_get_brightness(hellcreek,
0330 STATUS_OUT_SYNC_GOOD);
0331 }
0332
0333 hellcreek->led_sync_good.max_brightness = 1;
0334 hellcreek->led_sync_good.brightness_set = hellcreek_led_sync_good_set;
0335 hellcreek->led_sync_good.brightness_get = hellcreek_led_sync_good_get;
0336
0337 led = of_get_next_available_child(leds, led);
0338 if (!led) {
0339 dev_err(hellcreek->dev, "Second LED not specified!\n");
0340 ret = -EINVAL;
0341 goto out;
0342 }
0343
0344 ret = of_property_read_string(led, "label", &label);
0345 hellcreek->led_is_gm.name = ret ? "is_gm" : label;
0346
0347 ret = of_property_read_string(led, "default-state", &state);
0348 if (!ret) {
0349 if (!strcmp(state, "on"))
0350 hellcreek->led_is_gm.brightness = 1;
0351 else if (!strcmp(state, "off"))
0352 hellcreek->led_is_gm.brightness = 0;
0353 else if (!strcmp(state, "keep"))
0354 hellcreek->led_is_gm.brightness =
0355 hellcreek_get_brightness(hellcreek,
0356 STATUS_OUT_IS_GM);
0357 }
0358
0359 hellcreek->led_is_gm.max_brightness = 1;
0360 hellcreek->led_is_gm.brightness_set = hellcreek_led_is_gm_set;
0361 hellcreek->led_is_gm.brightness_get = hellcreek_led_is_gm_get;
0362
0363
0364 if (hellcreek->led_sync_good.brightness == 1)
0365 hellcreek_set_brightness(hellcreek, STATUS_OUT_SYNC_GOOD, 1);
0366 if (hellcreek->led_is_gm.brightness == 1)
0367 hellcreek_set_brightness(hellcreek, STATUS_OUT_IS_GM, 1);
0368
0369
0370 led_classdev_register(hellcreek->dev, &hellcreek->led_sync_good);
0371 led_classdev_register(hellcreek->dev, &hellcreek->led_is_gm);
0372
0373 ret = 0;
0374
0375 out:
0376 of_node_put(leds);
0377
0378 return ret;
0379 }
0380
0381 int hellcreek_ptp_setup(struct hellcreek *hellcreek)
0382 {
0383 u16 status;
0384 int ret;
0385
0386
0387 INIT_DELAYED_WORK(&hellcreek->overflow_work,
0388 hellcreek_ptp_overflow_check);
0389
0390
0391 hellcreek->ptp_clock_info.owner = THIS_MODULE;
0392 snprintf(hellcreek->ptp_clock_info.name,
0393 sizeof(hellcreek->ptp_clock_info.name),
0394 dev_name(hellcreek->dev));
0395
0396
0397
0398
0399
0400 hellcreek->ptp_clock_info.max_adj = 62500000;
0401 hellcreek->ptp_clock_info.n_alarm = 0;
0402 hellcreek->ptp_clock_info.n_pins = 0;
0403 hellcreek->ptp_clock_info.n_ext_ts = 0;
0404 hellcreek->ptp_clock_info.n_per_out = 0;
0405 hellcreek->ptp_clock_info.pps = 0;
0406 hellcreek->ptp_clock_info.adjfine = hellcreek_ptp_adjfine;
0407 hellcreek->ptp_clock_info.adjtime = hellcreek_ptp_adjtime;
0408 hellcreek->ptp_clock_info.gettime64 = hellcreek_ptp_gettime;
0409 hellcreek->ptp_clock_info.settime64 = hellcreek_ptp_settime;
0410 hellcreek->ptp_clock_info.enable = hellcreek_ptp_enable;
0411 hellcreek->ptp_clock_info.do_aux_work = hellcreek_hwtstamp_work;
0412
0413 hellcreek->ptp_clock = ptp_clock_register(&hellcreek->ptp_clock_info,
0414 hellcreek->dev);
0415 if (IS_ERR(hellcreek->ptp_clock))
0416 return PTR_ERR(hellcreek->ptp_clock);
0417
0418
0419
0420
0421 status = hellcreek_ptp_read(hellcreek, PR_CLOCK_STATUS_C);
0422 if (!(status & PR_CLOCK_STATUS_C_OFS_ACT))
0423 hellcreek_ptp_write(hellcreek,
0424 status | PR_CLOCK_STATUS_C_ENA_OFS,
0425 PR_CLOCK_STATUS_C);
0426
0427
0428 hellcreek_ptp_write(hellcreek, status | PR_CLOCK_STATUS_C_ENA_DRIFT,
0429 PR_CLOCK_STATUS_C);
0430
0431
0432 ret = hellcreek_led_setup(hellcreek);
0433 if (ret) {
0434 if (hellcreek->ptp_clock)
0435 ptp_clock_unregister(hellcreek->ptp_clock);
0436 return ret;
0437 }
0438
0439 schedule_delayed_work(&hellcreek->overflow_work,
0440 HELLCREEK_OVERFLOW_PERIOD);
0441
0442 return 0;
0443 }
0444
0445 void hellcreek_ptp_free(struct hellcreek *hellcreek)
0446 {
0447 led_classdev_unregister(&hellcreek->led_is_gm);
0448 led_classdev_unregister(&hellcreek->led_sync_good);
0449 cancel_delayed_work_sync(&hellcreek->overflow_work);
0450 if (hellcreek->ptp_clock)
0451 ptp_clock_unregister(hellcreek->ptp_clock);
0452 hellcreek->ptp_clock = NULL;
0453 }