1/* 2 * PTP 1588 clock using the IXP46X 3 * 4 * Copyright (C) 2010 OMICRON electronics GmbH 5 * 6 * This program is free software; you can redistribute it and/or modify 7 * it under the terms of the GNU General Public License as published by 8 * the Free Software Foundation; either version 2 of the License, or 9 * (at your option) any later version. 10 * 11 * This program is distributed in the hope that it will be useful, 12 * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 * GNU General Public License for more details. 15 * 16 * You should have received a copy of the GNU General Public License 17 * along with this program; if not, write to the Free Software 18 * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. 19 */ 20#include <linux/device.h> 21#include <linux/err.h> 22#include <linux/gpio.h> 23#include <linux/init.h> 24#include <linux/interrupt.h> 25#include <linux/io.h> 26#include <linux/irq.h> 27#include <linux/kernel.h> 28#include <linux/module.h> 29 30#include <linux/ptp_clock_kernel.h> 31#include <mach/ixp46x_ts.h> 32 33#define DRIVER "ptp_ixp46x" 34#define N_EXT_TS 2 35#define MASTER_GPIO 8 36#define MASTER_IRQ 25 37#define SLAVE_GPIO 7 38#define SLAVE_IRQ 24 39 40struct ixp_clock { 41 struct ixp46x_ts_regs *regs; 42 struct ptp_clock *ptp_clock; 43 struct ptp_clock_info caps; 44 int exts0_enabled; 45 int exts1_enabled; 46}; 47 48DEFINE_SPINLOCK(register_lock); 49 50/* 51 * Register access functions 52 */ 53 54static u64 ixp_systime_read(struct ixp46x_ts_regs *regs) 55{ 56 u64 ns; 57 u32 lo, hi; 58 59 lo = __raw_readl(®s->systime_lo); 60 hi = __raw_readl(®s->systime_hi); 61 62 ns = ((u64) hi) << 32; 63 ns |= lo; 64 ns <<= TICKS_NS_SHIFT; 65 66 return ns; 67} 68 69static void ixp_systime_write(struct ixp46x_ts_regs *regs, u64 ns) 70{ 71 u32 hi, lo; 72 73 ns >>= TICKS_NS_SHIFT; 74 hi = ns >> 32; 75 lo = ns & 0xffffffff; 76 77 __raw_writel(lo, ®s->systime_lo); 78 __raw_writel(hi, ®s->systime_hi); 79} 80 81/* 82 * Interrupt service routine 83 */ 84 85static irqreturn_t isr(int irq, void *priv) 86{ 87 struct ixp_clock *ixp_clock = priv; 88 struct ixp46x_ts_regs *regs = ixp_clock->regs; 89 struct ptp_clock_event event; 90 u32 ack = 0, lo, hi, val; 91 92 val = __raw_readl(®s->event); 93 94 if (val & TSER_SNS) { 95 ack |= TSER_SNS; 96 if (ixp_clock->exts0_enabled) { 97 hi = __raw_readl(®s->asms_hi); 98 lo = __raw_readl(®s->asms_lo); 99 event.type = PTP_CLOCK_EXTTS; 100 event.index = 0; 101 event.timestamp = ((u64) hi) << 32; 102 event.timestamp |= lo; 103 event.timestamp <<= TICKS_NS_SHIFT; 104 ptp_clock_event(ixp_clock->ptp_clock, &event); 105 } 106 } 107 108 if (val & TSER_SNM) { 109 ack |= TSER_SNM; 110 if (ixp_clock->exts1_enabled) { 111 hi = __raw_readl(®s->amms_hi); 112 lo = __raw_readl(®s->amms_lo); 113 event.type = PTP_CLOCK_EXTTS; 114 event.index = 1; 115 event.timestamp = ((u64) hi) << 32; 116 event.timestamp |= lo; 117 event.timestamp <<= TICKS_NS_SHIFT; 118 ptp_clock_event(ixp_clock->ptp_clock, &event); 119 } 120 } 121 122 if (val & TTIPEND) 123 ack |= TTIPEND; /* this bit seems to be always set */ 124 125 if (ack) { 126 __raw_writel(ack, ®s->event); 127 return IRQ_HANDLED; 128 } else 129 return IRQ_NONE; 130} 131 132/* 133 * PTP clock operations 134 */ 135 136static int ptp_ixp_adjfreq(struct ptp_clock_info *ptp, s32 ppb) 137{ 138 u64 adj; 139 u32 diff, addend; 140 int neg_adj = 0; 141 struct ixp_clock *ixp_clock = container_of(ptp, struct ixp_clock, caps); 142 struct ixp46x_ts_regs *regs = ixp_clock->regs; 143 144 if (ppb < 0) { 145 neg_adj = 1; 146 ppb = -ppb; 147 } 148 addend = DEFAULT_ADDEND; 149 adj = addend; 150 adj *= ppb; 151 diff = div_u64(adj, 1000000000ULL); 152 153 addend = neg_adj ? addend - diff : addend + diff; 154 155 __raw_writel(addend, ®s->addend); 156 157 return 0; 158} 159 160static int ptp_ixp_adjtime(struct ptp_clock_info *ptp, s64 delta) 161{ 162 s64 now; 163 unsigned long flags; 164 struct ixp_clock *ixp_clock = container_of(ptp, struct ixp_clock, caps); 165 struct ixp46x_ts_regs *regs = ixp_clock->regs; 166 167 spin_lock_irqsave(®ister_lock, flags); 168 169 now = ixp_systime_read(regs); 170 now += delta; 171 ixp_systime_write(regs, now); 172 173 spin_unlock_irqrestore(®ister_lock, flags); 174 175 return 0; 176} 177 178static int ptp_ixp_gettime(struct ptp_clock_info *ptp, struct timespec64 *ts) 179{ 180 u64 ns; 181 u32 remainder; 182 unsigned long flags; 183 struct ixp_clock *ixp_clock = container_of(ptp, struct ixp_clock, caps); 184 struct ixp46x_ts_regs *regs = ixp_clock->regs; 185 186 spin_lock_irqsave(®ister_lock, flags); 187 188 ns = ixp_systime_read(regs); 189 190 spin_unlock_irqrestore(®ister_lock, flags); 191 192 ts->tv_sec = div_u64_rem(ns, 1000000000, &remainder); 193 ts->tv_nsec = remainder; 194 return 0; 195} 196 197static int ptp_ixp_settime(struct ptp_clock_info *ptp, 198 const struct timespec64 *ts) 199{ 200 u64 ns; 201 unsigned long flags; 202 struct ixp_clock *ixp_clock = container_of(ptp, struct ixp_clock, caps); 203 struct ixp46x_ts_regs *regs = ixp_clock->regs; 204 205 ns = ts->tv_sec * 1000000000ULL; 206 ns += ts->tv_nsec; 207 208 spin_lock_irqsave(®ister_lock, flags); 209 210 ixp_systime_write(regs, ns); 211 212 spin_unlock_irqrestore(®ister_lock, flags); 213 214 return 0; 215} 216 217static int ptp_ixp_enable(struct ptp_clock_info *ptp, 218 struct ptp_clock_request *rq, int on) 219{ 220 struct ixp_clock *ixp_clock = container_of(ptp, struct ixp_clock, caps); 221 222 switch (rq->type) { 223 case PTP_CLK_REQ_EXTTS: 224 switch (rq->extts.index) { 225 case 0: 226 ixp_clock->exts0_enabled = on ? 1 : 0; 227 break; 228 case 1: 229 ixp_clock->exts1_enabled = on ? 1 : 0; 230 break; 231 default: 232 return -EINVAL; 233 } 234 return 0; 235 default: 236 break; 237 } 238 239 return -EOPNOTSUPP; 240} 241 242static struct ptp_clock_info ptp_ixp_caps = { 243 .owner = THIS_MODULE, 244 .name = "IXP46X timer", 245 .max_adj = 66666655, 246 .n_ext_ts = N_EXT_TS, 247 .n_pins = 0, 248 .pps = 0, 249 .adjfreq = ptp_ixp_adjfreq, 250 .adjtime = ptp_ixp_adjtime, 251 .gettime64 = ptp_ixp_gettime, 252 .settime64 = ptp_ixp_settime, 253 .enable = ptp_ixp_enable, 254}; 255 256/* module operations */ 257 258static struct ixp_clock ixp_clock; 259 260static int setup_interrupt(int gpio) 261{ 262 int irq; 263 int err; 264 265 err = gpio_request(gpio, "ixp4-ptp"); 266 if (err) 267 return err; 268 269 err = gpio_direction_input(gpio); 270 if (err) 271 return err; 272 273 irq = gpio_to_irq(gpio); 274 275 if (NO_IRQ == irq) 276 return NO_IRQ; 277 278 if (irq_set_irq_type(irq, IRQF_TRIGGER_FALLING)) { 279 pr_err("cannot set trigger type for irq %d\n", irq); 280 return NO_IRQ; 281 } 282 283 if (request_irq(irq, isr, 0, DRIVER, &ixp_clock)) { 284 pr_err("request_irq failed for irq %d\n", irq); 285 return NO_IRQ; 286 } 287 288 return irq; 289} 290 291static void __exit ptp_ixp_exit(void) 292{ 293 free_irq(MASTER_IRQ, &ixp_clock); 294 free_irq(SLAVE_IRQ, &ixp_clock); 295 ixp46x_phc_index = -1; 296 ptp_clock_unregister(ixp_clock.ptp_clock); 297} 298 299static int __init ptp_ixp_init(void) 300{ 301 if (!cpu_is_ixp46x()) 302 return -ENODEV; 303 304 ixp_clock.regs = 305 (struct ixp46x_ts_regs __iomem *) IXP4XX_TIMESYNC_BASE_VIRT; 306 307 ixp_clock.caps = ptp_ixp_caps; 308 309 ixp_clock.ptp_clock = ptp_clock_register(&ixp_clock.caps, NULL); 310 311 if (IS_ERR(ixp_clock.ptp_clock)) 312 return PTR_ERR(ixp_clock.ptp_clock); 313 314 ixp46x_phc_index = ptp_clock_index(ixp_clock.ptp_clock); 315 316 __raw_writel(DEFAULT_ADDEND, &ixp_clock.regs->addend); 317 __raw_writel(1, &ixp_clock.regs->trgt_lo); 318 __raw_writel(0, &ixp_clock.regs->trgt_hi); 319 __raw_writel(TTIPEND, &ixp_clock.regs->event); 320 321 if (MASTER_IRQ != setup_interrupt(MASTER_GPIO)) { 322 pr_err("failed to setup gpio %d as irq\n", MASTER_GPIO); 323 goto no_master; 324 } 325 if (SLAVE_IRQ != setup_interrupt(SLAVE_GPIO)) { 326 pr_err("failed to setup gpio %d as irq\n", SLAVE_GPIO); 327 goto no_slave; 328 } 329 330 return 0; 331no_slave: 332 free_irq(MASTER_IRQ, &ixp_clock); 333no_master: 334 ptp_clock_unregister(ixp_clock.ptp_clock); 335 return -ENODEV; 336} 337 338module_init(ptp_ixp_init); 339module_exit(ptp_ixp_exit); 340 341MODULE_AUTHOR("Richard Cochran <richardcochran@gmail.com>"); 342MODULE_DESCRIPTION("PTP clock using the IXP46X timer"); 343MODULE_LICENSE("GPL"); 344