diff options
| author | Arun Parameswaran <arun.parameswaran@broadcom.com> | 2017-06-12 16:26:01 -0400 |
|---|---|---|
| committer | David S. Miller <davem@davemloft.net> | 2017-06-15 12:07:15 -0400 |
| commit | 8a56aa107f1e812347cc1209aa674e1361375af8 (patch) | |
| tree | ff69ae17b51913756ab48a76c72a6d69efe9c179 /drivers/ptp | |
| parent | 80d6076140b2bc0e6b9951f6aacc3de55ceb37fd (diff) | |
ptp: Add a ptp clock driver for Broadcom DTE
This patch adds a ptp clock driver for the Broadcom SoCs using
the Digital timing Engine (DTE) nco.
Signed-off-by: Arun Parameswaran <arun.parameswaran@broadcom.com>
Signed-off-by: David S. Miller <davem@davemloft.net>
Diffstat (limited to 'drivers/ptp')
| -rw-r--r-- | drivers/ptp/Kconfig | 16 | ||||
| -rw-r--r-- | drivers/ptp/Makefile | 1 | ||||
| -rw-r--r-- | drivers/ptp/ptp_dte.c | 353 |
3 files changed, 370 insertions, 0 deletions
diff --git a/drivers/ptp/Kconfig b/drivers/ptp/Kconfig index 384f661a6496..a21ad10d613c 100644 --- a/drivers/ptp/Kconfig +++ b/drivers/ptp/Kconfig | |||
| @@ -25,6 +25,22 @@ config PTP_1588_CLOCK | |||
| 25 | To compile this driver as a module, choose M here: the module | 25 | To compile this driver as a module, choose M here: the module |
| 26 | will be called ptp. | 26 | will be called ptp. |
| 27 | 27 | ||
| 28 | config PTP_1588_CLOCK_DTE | ||
| 29 | tristate "Broadcom DTE as PTP clock" | ||
| 30 | depends on PTP_1588_CLOCK | ||
| 31 | depends on NET && HAS_IOMEM | ||
| 32 | depends on ARCH_BCM_MOBILE || (ARCH_BCM_IPROC && !(ARCH_BCM_NSP || ARCH_BCM_5301X)) || COMPILE_TEST | ||
| 33 | default y | ||
| 34 | help | ||
| 35 | This driver adds support for using the Digital timing engine | ||
| 36 | (DTE) in the Broadcom SoC's as a PTP clock. | ||
| 37 | |||
| 38 | The clock can be used in both wired and wireless networks | ||
| 39 | for PTP purposes. | ||
| 40 | |||
| 41 | To compile this driver as a module, choose M here: the module | ||
| 42 | will be called ptp_dte. | ||
| 43 | |||
| 28 | config PTP_1588_CLOCK_GIANFAR | 44 | config PTP_1588_CLOCK_GIANFAR |
| 29 | tristate "Freescale eTSEC as PTP clock" | 45 | tristate "Freescale eTSEC as PTP clock" |
| 30 | depends on GIANFAR | 46 | depends on GIANFAR |
diff --git a/drivers/ptp/Makefile b/drivers/ptp/Makefile index 530736161a8b..d1f2fb19c980 100644 --- a/drivers/ptp/Makefile +++ b/drivers/ptp/Makefile | |||
| @@ -4,6 +4,7 @@ | |||
| 4 | 4 | ||
| 5 | ptp-y := ptp_clock.o ptp_chardev.o ptp_sysfs.o | 5 | ptp-y := ptp_clock.o ptp_chardev.o ptp_sysfs.o |
| 6 | obj-$(CONFIG_PTP_1588_CLOCK) += ptp.o | 6 | obj-$(CONFIG_PTP_1588_CLOCK) += ptp.o |
| 7 | obj-$(CONFIG_PTP_1588_CLOCK_DTE) += ptp_dte.o | ||
| 7 | obj-$(CONFIG_PTP_1588_CLOCK_IXP46X) += ptp_ixp46x.o | 8 | obj-$(CONFIG_PTP_1588_CLOCK_IXP46X) += ptp_ixp46x.o |
| 8 | obj-$(CONFIG_PTP_1588_CLOCK_PCH) += ptp_pch.o | 9 | obj-$(CONFIG_PTP_1588_CLOCK_PCH) += ptp_pch.o |
| 9 | obj-$(CONFIG_PTP_1588_CLOCK_KVM) += ptp_kvm.o | 10 | obj-$(CONFIG_PTP_1588_CLOCK_KVM) += ptp_kvm.o |
diff --git a/drivers/ptp/ptp_dte.c b/drivers/ptp/ptp_dte.c new file mode 100644 index 000000000000..00145a3f1e70 --- /dev/null +++ b/drivers/ptp/ptp_dte.c | |||
| @@ -0,0 +1,353 @@ | |||
| 1 | /* | ||
| 2 | * Copyright 2017 Broadcom | ||
| 3 | * | ||
| 4 | * This program is free software; you can redistribute it and/or | ||
| 5 | * modify it under the terms of the GNU General Public License as | ||
| 6 | * published by the Free Software Foundation version 2. | ||
| 7 | * | ||
| 8 | * This program is distributed "as is" WITHOUT ANY WARRANTY of any | ||
| 9 | * kind, whether express or implied; without even the implied warranty | ||
| 10 | * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | ||
| 11 | * GNU General Public License for more details. | ||
| 12 | */ | ||
| 13 | |||
| 14 | #include <linux/err.h> | ||
| 15 | #include <linux/io.h> | ||
| 16 | #include <linux/module.h> | ||
| 17 | #include <linux/platform_device.h> | ||
| 18 | #include <linux/ptp_clock_kernel.h> | ||
| 19 | #include <linux/types.h> | ||
| 20 | |||
| 21 | #define DTE_NCO_LOW_TIME_REG 0x00 | ||
| 22 | #define DTE_NCO_TIME_REG 0x04 | ||
| 23 | #define DTE_NCO_OVERFLOW_REG 0x08 | ||
| 24 | #define DTE_NCO_INC_REG 0x0c | ||
| 25 | |||
| 26 | #define DTE_NCO_SUM2_MASK 0xffffffff | ||
| 27 | #define DTE_NCO_SUM2_SHIFT 4ULL | ||
| 28 | |||
| 29 | #define DTE_NCO_SUM3_MASK 0xff | ||
| 30 | #define DTE_NCO_SUM3_SHIFT 36ULL | ||
| 31 | #define DTE_NCO_SUM3_WR_SHIFT 8 | ||
| 32 | |||
| 33 | #define DTE_NCO_TS_WRAP_MASK 0xfff | ||
| 34 | #define DTE_NCO_TS_WRAP_LSHIFT 32 | ||
| 35 | |||
| 36 | #define DTE_NCO_INC_DEFAULT 0x80000000 | ||
| 37 | #define DTE_NUM_REGS_TO_RESTORE 4 | ||
| 38 | |||
| 39 | /* Full wrap around is 44bits in ns (~4.887 hrs) */ | ||
| 40 | #define DTE_WRAP_AROUND_NSEC_SHIFT 44 | ||
| 41 | |||
| 42 | /* 44 bits NCO */ | ||
| 43 | #define DTE_NCO_MAX_NS 0xFFFFFFFFFFF | ||
| 44 | |||
| 45 | /* 125MHz with 3.29 reg cfg */ | ||
| 46 | #define DTE_PPB_ADJ(ppb) (u32)(div64_u64((((u64)abs(ppb) * BIT(28)) +\ | ||
| 47 | 62500000ULL), 125000000ULL)) | ||
| 48 | |||
| 49 | /* ptp dte priv structure */ | ||
| 50 | struct ptp_dte { | ||
| 51 | void __iomem *regs; | ||
| 52 | struct ptp_clock *ptp_clk; | ||
| 53 | struct ptp_clock_info caps; | ||
| 54 | struct device *dev; | ||
| 55 | u32 ts_ovf_last; | ||
| 56 | u32 ts_wrap_cnt; | ||
| 57 | spinlock_t lock; | ||
| 58 | u32 reg_val[DTE_NUM_REGS_TO_RESTORE]; | ||
| 59 | }; | ||
| 60 | |||
| 61 | static void dte_write_nco(void __iomem *regs, s64 ns) | ||
| 62 | { | ||
| 63 | u32 sum2, sum3; | ||
| 64 | |||
| 65 | sum2 = (u32)((ns >> DTE_NCO_SUM2_SHIFT) & DTE_NCO_SUM2_MASK); | ||
| 66 | /* compensate for ignoring sum1 */ | ||
| 67 | if (sum2 != DTE_NCO_SUM2_MASK) | ||
| 68 | sum2++; | ||
| 69 | |||
| 70 | /* to write sum3, bits [15:8] needs to be written */ | ||
| 71 | sum3 = (u32)(((ns >> DTE_NCO_SUM3_SHIFT) & DTE_NCO_SUM3_MASK) << | ||
| 72 | DTE_NCO_SUM3_WR_SHIFT); | ||
| 73 | |||
| 74 | writel(0, (regs + DTE_NCO_LOW_TIME_REG)); | ||
| 75 | writel(sum2, (regs + DTE_NCO_TIME_REG)); | ||
| 76 | writel(sum3, (regs + DTE_NCO_OVERFLOW_REG)); | ||
| 77 | } | ||
| 78 | |||
| 79 | static s64 dte_read_nco(void __iomem *regs) | ||
| 80 | { | ||
| 81 | u32 sum2, sum3; | ||
| 82 | s64 ns; | ||
| 83 | |||
| 84 | /* | ||
| 85 | * ignoring sum1 (4 bits) gives a 16ns resolution, which | ||
| 86 | * works due to the async register read. | ||
| 87 | */ | ||
| 88 | sum3 = readl(regs + DTE_NCO_OVERFLOW_REG) & DTE_NCO_SUM3_MASK; | ||
| 89 | sum2 = readl(regs + DTE_NCO_TIME_REG); | ||
| 90 | ns = ((s64)sum3 << DTE_NCO_SUM3_SHIFT) | | ||
| 91 | ((s64)sum2 << DTE_NCO_SUM2_SHIFT); | ||
| 92 | |||
| 93 | return ns; | ||
| 94 | } | ||
| 95 | |||
| 96 | static void dte_write_nco_delta(struct ptp_dte *ptp_dte, s64 delta) | ||
| 97 | { | ||
| 98 | s64 ns; | ||
| 99 | |||
| 100 | ns = dte_read_nco(ptp_dte->regs); | ||
| 101 | |||
| 102 | /* handle wraparound conditions */ | ||
| 103 | if ((delta < 0) && (abs(delta) > ns)) { | ||
| 104 | if (ptp_dte->ts_wrap_cnt) { | ||
| 105 | ns += DTE_NCO_MAX_NS + delta; | ||
| 106 | ptp_dte->ts_wrap_cnt--; | ||
| 107 | } else { | ||
| 108 | ns = 0; | ||
| 109 | } | ||
| 110 | } else { | ||
| 111 | ns += delta; | ||
| 112 | if (ns > DTE_NCO_MAX_NS) { | ||
| 113 | ptp_dte->ts_wrap_cnt++; | ||
| 114 | ns -= DTE_NCO_MAX_NS; | ||
| 115 | } | ||
| 116 | } | ||
| 117 | |||
| 118 | dte_write_nco(ptp_dte->regs, ns); | ||
| 119 | |||
| 120 | ptp_dte->ts_ovf_last = (ns >> DTE_NCO_TS_WRAP_LSHIFT) & | ||
| 121 | DTE_NCO_TS_WRAP_MASK; | ||
| 122 | } | ||
| 123 | |||
| 124 | static s64 dte_read_nco_with_ovf(struct ptp_dte *ptp_dte) | ||
| 125 | { | ||
| 126 | u32 ts_ovf; | ||
| 127 | s64 ns = 0; | ||
| 128 | |||
| 129 | ns = dte_read_nco(ptp_dte->regs); | ||
| 130 | |||
| 131 | /*Timestamp overflow: 8 LSB bits of sum3, 4 MSB bits of sum2 */ | ||
| 132 | ts_ovf = (ns >> DTE_NCO_TS_WRAP_LSHIFT) & DTE_NCO_TS_WRAP_MASK; | ||
| 133 | |||
| 134 | /* Check for wrap around */ | ||
| 135 | if (ts_ovf < ptp_dte->ts_ovf_last) | ||
| 136 | ptp_dte->ts_wrap_cnt++; | ||
| 137 | |||
| 138 | ptp_dte->ts_ovf_last = ts_ovf; | ||
| 139 | |||
| 140 | /* adjust for wraparounds */ | ||
| 141 | ns += (s64)(BIT_ULL(DTE_WRAP_AROUND_NSEC_SHIFT) * ptp_dte->ts_wrap_cnt); | ||
| 142 | |||
| 143 | return ns; | ||
| 144 | } | ||
| 145 | |||
| 146 | static int ptp_dte_adjfreq(struct ptp_clock_info *ptp, s32 ppb) | ||
| 147 | { | ||
| 148 | u32 nco_incr; | ||
| 149 | unsigned long flags; | ||
| 150 | struct ptp_dte *ptp_dte = container_of(ptp, struct ptp_dte, caps); | ||
| 151 | |||
| 152 | if (abs(ppb) > ptp_dte->caps.max_adj) { | ||
| 153 | dev_err(ptp_dte->dev, "ppb adj too big\n"); | ||
| 154 | return -EINVAL; | ||
| 155 | } | ||
| 156 | |||
| 157 | if (ppb < 0) | ||
| 158 | nco_incr = DTE_NCO_INC_DEFAULT - DTE_PPB_ADJ(ppb); | ||
| 159 | else | ||
| 160 | nco_incr = DTE_NCO_INC_DEFAULT + DTE_PPB_ADJ(ppb); | ||
| 161 | |||
| 162 | spin_lock_irqsave(&ptp_dte->lock, flags); | ||
| 163 | writel(nco_incr, ptp_dte->regs + DTE_NCO_INC_REG); | ||
| 164 | spin_unlock_irqrestore(&ptp_dte->lock, flags); | ||
| 165 | |||
| 166 | return 0; | ||
| 167 | } | ||
| 168 | |||
| 169 | static int ptp_dte_adjtime(struct ptp_clock_info *ptp, s64 delta) | ||
| 170 | { | ||
| 171 | unsigned long flags; | ||
| 172 | struct ptp_dte *ptp_dte = container_of(ptp, struct ptp_dte, caps); | ||
| 173 | |||
| 174 | spin_lock_irqsave(&ptp_dte->lock, flags); | ||
| 175 | dte_write_nco_delta(ptp_dte, delta); | ||
| 176 | spin_unlock_irqrestore(&ptp_dte->lock, flags); | ||
| 177 | |||
| 178 | return 0; | ||
| 179 | } | ||
| 180 | |||
| 181 | static int ptp_dte_gettime(struct ptp_clock_info *ptp, struct timespec64 *ts) | ||
| 182 | { | ||
| 183 | unsigned long flags; | ||
| 184 | struct ptp_dte *ptp_dte = container_of(ptp, struct ptp_dte, caps); | ||
| 185 | |||
| 186 | spin_lock_irqsave(&ptp_dte->lock, flags); | ||
| 187 | *ts = ns_to_timespec64(dte_read_nco_with_ovf(ptp_dte)); | ||
| 188 | spin_unlock_irqrestore(&ptp_dte->lock, flags); | ||
| 189 | |||
| 190 | return 0; | ||
| 191 | } | ||
| 192 | |||
| 193 | static int ptp_dte_settime(struct ptp_clock_info *ptp, | ||
| 194 | const struct timespec64 *ts) | ||
| 195 | { | ||
| 196 | unsigned long flags; | ||
| 197 | struct ptp_dte *ptp_dte = container_of(ptp, struct ptp_dte, caps); | ||
| 198 | |||
| 199 | spin_lock_irqsave(&ptp_dte->lock, flags); | ||
| 200 | |||
| 201 | /* Disable nco increment */ | ||
| 202 | writel(0, ptp_dte->regs + DTE_NCO_INC_REG); | ||
| 203 | |||
| 204 | dte_write_nco(ptp_dte->regs, timespec64_to_ns(ts)); | ||
| 205 | |||
| 206 | /* reset overflow and wrap counter */ | ||
| 207 | ptp_dte->ts_ovf_last = 0; | ||
| 208 | ptp_dte->ts_wrap_cnt = 0; | ||
| 209 | |||
| 210 | /* Enable nco increment */ | ||
| 211 | writel(DTE_NCO_INC_DEFAULT, ptp_dte->regs + DTE_NCO_INC_REG); | ||
| 212 | |||
| 213 | spin_unlock_irqrestore(&ptp_dte->lock, flags); | ||
| 214 | |||
| 215 | return 0; | ||
| 216 | } | ||
| 217 | |||
| 218 | static int ptp_dte_enable(struct ptp_clock_info *ptp, | ||
| 219 | struct ptp_clock_request *rq, int on) | ||
| 220 | { | ||
| 221 | return -EOPNOTSUPP; | ||
| 222 | } | ||
| 223 | |||
| 224 | static struct ptp_clock_info ptp_dte_caps = { | ||
| 225 | .owner = THIS_MODULE, | ||
| 226 | .name = "DTE PTP timer", | ||
| 227 | .max_adj = 50000000, | ||
| 228 | .n_ext_ts = 0, | ||
| 229 | .n_pins = 0, | ||
| 230 | .pps = 0, | ||
| 231 | .adjfreq = ptp_dte_adjfreq, | ||
| 232 | .adjtime = ptp_dte_adjtime, | ||
| 233 | .gettime64 = ptp_dte_gettime, | ||
| 234 | .settime64 = ptp_dte_settime, | ||
| 235 | .enable = ptp_dte_enable, | ||
| 236 | }; | ||
| 237 | |||
| 238 | static int ptp_dte_probe(struct platform_device *pdev) | ||
| 239 | { | ||
| 240 | struct ptp_dte *ptp_dte; | ||
| 241 | struct device *dev = &pdev->dev; | ||
| 242 | struct resource *res; | ||
| 243 | |||
| 244 | ptp_dte = devm_kzalloc(dev, sizeof(struct ptp_dte), GFP_KERNEL); | ||
| 245 | if (!ptp_dte) | ||
| 246 | return -ENOMEM; | ||
| 247 | |||
| 248 | res = platform_get_resource(pdev, IORESOURCE_MEM, 0); | ||
| 249 | ptp_dte->regs = devm_ioremap_resource(dev, res); | ||
| 250 | if (IS_ERR(ptp_dte->regs)) { | ||
| 251 | dev_err(dev, | ||
| 252 | "%s: io remap failed\n", __func__); | ||
| 253 | return PTR_ERR(ptp_dte->regs); | ||
| 254 | } | ||
| 255 | |||
| 256 | spin_lock_init(&ptp_dte->lock); | ||
| 257 | |||
| 258 | ptp_dte->dev = dev; | ||
| 259 | ptp_dte->caps = ptp_dte_caps; | ||
| 260 | ptp_dte->ptp_clk = ptp_clock_register(&ptp_dte->caps, &pdev->dev); | ||
| 261 | if (IS_ERR(ptp_dte->ptp_clk)) { | ||
| 262 | dev_err(dev, | ||
| 263 | "%s: Failed to register ptp clock\n", __func__); | ||
| 264 | return PTR_ERR(ptp_dte->ptp_clk); | ||
| 265 | } | ||
| 266 | |||
| 267 | platform_set_drvdata(pdev, ptp_dte); | ||
| 268 | |||
| 269 | dev_info(dev, "ptp clk probe done\n"); | ||
| 270 | |||
| 271 | return 0; | ||
| 272 | } | ||
| 273 | |||
| 274 | static int ptp_dte_remove(struct platform_device *pdev) | ||
| 275 | { | ||
| 276 | struct ptp_dte *ptp_dte = platform_get_drvdata(pdev); | ||
| 277 | u8 i; | ||
| 278 | |||
| 279 | ptp_clock_unregister(ptp_dte->ptp_clk); | ||
| 280 | |||
| 281 | for (i = 0; i < DTE_NUM_REGS_TO_RESTORE; i++) | ||
| 282 | writel(0, ptp_dte->regs + (i * sizeof(u32))); | ||
| 283 | |||
| 284 | return 0; | ||
| 285 | } | ||
| 286 | |||
| 287 | #ifdef CONFIG_PM_SLEEP | ||
| 288 | static int ptp_dte_suspend(struct device *dev) | ||
| 289 | { | ||
| 290 | struct platform_device *pdev = to_platform_device(dev); | ||
| 291 | struct ptp_dte *ptp_dte = platform_get_drvdata(pdev); | ||
| 292 | u8 i; | ||
| 293 | |||
| 294 | for (i = 0; i < DTE_NUM_REGS_TO_RESTORE; i++) { | ||
| 295 | ptp_dte->reg_val[i] = | ||
| 296 | readl(ptp_dte->regs + (i * sizeof(u32))); | ||
| 297 | } | ||
| 298 | |||
| 299 | /* disable the nco */ | ||
| 300 | writel(0, ptp_dte->regs + DTE_NCO_INC_REG); | ||
| 301 | |||
| 302 | return 0; | ||
| 303 | } | ||
| 304 | |||
| 305 | static int ptp_dte_resume(struct device *dev) | ||
| 306 | { | ||
| 307 | struct platform_device *pdev = to_platform_device(dev); | ||
| 308 | struct ptp_dte *ptp_dte = platform_get_drvdata(pdev); | ||
| 309 | u8 i; | ||
| 310 | |||
| 311 | for (i = 0; i < DTE_NUM_REGS_TO_RESTORE; i++) { | ||
| 312 | if ((i * sizeof(u32)) != DTE_NCO_OVERFLOW_REG) | ||
| 313 | writel(ptp_dte->reg_val[i], | ||
| 314 | (ptp_dte->regs + (i * sizeof(u32)))); | ||
| 315 | else | ||
| 316 | writel(((ptp_dte->reg_val[i] & | ||
| 317 | DTE_NCO_SUM3_MASK) << DTE_NCO_SUM3_WR_SHIFT), | ||
| 318 | (ptp_dte->regs + (i * sizeof(u32)))); | ||
| 319 | } | ||
| 320 | |||
| 321 | return 0; | ||
| 322 | } | ||
| 323 | |||
| 324 | static const struct dev_pm_ops ptp_dte_pm_ops = { | ||
| 325 | .suspend = ptp_dte_suspend, | ||
| 326 | .resume = ptp_dte_resume | ||
| 327 | }; | ||
| 328 | |||
| 329 | #define PTP_DTE_PM_OPS (&ptp_dte_pm_ops) | ||
| 330 | #else | ||
| 331 | #define PTP_DTE_PM_OPS NULL | ||
| 332 | #endif | ||
| 333 | |||
| 334 | static const struct of_device_id ptp_dte_of_match[] = { | ||
| 335 | { .compatible = "brcm,ptp-dte", }, | ||
| 336 | {}, | ||
| 337 | }; | ||
| 338 | MODULE_DEVICE_TABLE(of, ptp_dte_of_match); | ||
| 339 | |||
| 340 | static struct platform_driver ptp_dte_driver = { | ||
| 341 | .driver = { | ||
| 342 | .name = "ptp-dte", | ||
| 343 | .pm = PTP_DTE_PM_OPS, | ||
| 344 | .of_match_table = ptp_dte_of_match, | ||
| 345 | }, | ||
| 346 | .probe = ptp_dte_probe, | ||
| 347 | .remove = ptp_dte_remove, | ||
| 348 | }; | ||
| 349 | module_platform_driver(ptp_dte_driver); | ||
| 350 | |||
| 351 | MODULE_AUTHOR("Broadcom"); | ||
| 352 | MODULE_DESCRIPTION("Broadcom DTE PTP Clock driver"); | ||
| 353 | MODULE_LICENSE("GPL v2"); | ||
