/* Copyright (c) 2014, The Linux Foundation. All rights reserved. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License version 2 and * only version 2 as published by the Free Software Foundation. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. */ #include #include #include #include #include #include #include #include #include #include #define PP2S_MODULE_NAME "PP2S" #define WC_MODULE_NAME "wallclock" /* * Logging macros... */ #define LOG_DRVR_DEBUG(args...) pr_debug(args) #define LOG_DRVR_ERR(args...) pr_err(args) /* * The following defines how many sysfs nodes the driver is creating. */ #define NUM_PP2S_SYSFS_REG_NODES 1 #define NUM_WC_SYSFS_REG_NODES 8 #define ATTR_PTR_SIZE sizeof(struct attribute *) /* * The following deal with register locations in virtual memory. * * More specifically, we're mapping in base addresses and a length * (that come from the device tree) during the probe and then the * registers are just offsets from their respective base... */ #define BASEADDR_PLUS(base, off) \ ((void *) ((u32) (base) + (u32) (off))) #define WC_SECS_ADDR BASEADDR_PLUS(time_bank, 0x00000014) #define WC_NSECS_ADDR BASEADDR_PLUS(time_bank, 0x00000018) #define WC_CONTROL_ADDR BASEADDR_PLUS(cntrl_bank1, 0x00000020) #define WC_PULSECNT_ADDR BASEADDR_PLUS(cntrl_bank1, 0x00000024) #define WC_CLOCKCNT_ADDR BASEADDR_PLUS(cntrl_bank1, 0x0000002C) #define WC_SNAPSHOT_ADDR BASEADDR_PLUS(cntrl_bank1, 0x00000028) #define WC_SFN_ADDR BASEADDR_PLUS(cntrl_bank2, 0x00000000) /* * Number of bits in the nanosecond register to be used for the * storage of leap seconds... */ #define BITS_FOR_LEAP 6 /* * The rate the free running clock runs at... */ #define CLK_RATE 122880000 /* * The difference (in seconds) between the unix and gps epochs... */ #define UNIX_EPOCH_TO_GPS_EPOCH_GAP 315964800 /* * These macros assist with creating sysfs attributes... */ #define MAKE_RO_ATTR(_name, _ptr, _index) \ (_ptr)->kobj_attr_##_name.attr.name = __stringify(_name); \ (_ptr)->kobj_attr_##_name.attr.mode = S_IRUSR; \ (_ptr)->kobj_attr_##_name.show = read_##_name; \ (_ptr)->kobj_attr_##_name.store = NULL; \ sysfs_attr_init(&(_ptr)->kobj_attr_##_name.attr); \ (_ptr)->attr_grp.attrs[_index] = &(_ptr)->kobj_attr_##_name.attr; #define MAKE_RW_ATTR(_name, _ptr, _index) \ (_ptr)->kobj_attr_##_name.attr.name = __stringify(_name); \ (_ptr)->kobj_attr_##_name.attr.mode = S_IRUSR|S_IWUSR; \ (_ptr)->kobj_attr_##_name.show = read_##_name; \ (_ptr)->kobj_attr_##_name.store = write_##_name; \ sysfs_attr_init(&(_ptr)->kobj_attr_##_name.attr); \ (_ptr)->attr_grp.attrs[_index] = &(_ptr)->kobj_attr_##_name.attr; /* * The following will break any pending poll/select(s) on a sysfs * node... */ #define RELEASE_SELECT_ON_ATTR(_ptr, _attr) \ sysfs_notify((_ptr)->kobj, NULL, __stringify(_attr)) /* * The following is for the creation of pp2s sysfs entries. */ struct pp2s_sysfs_t { struct kobject *kobj; struct attribute_group attr_grp; struct kobj_attribute kobj_attr_pp2s_cnt; unsigned long long pp2s_cnt; }; /* * The following is for the creation of wallclock sysfs entries. */ struct wc_sysfs_t { struct kobject *kobj; struct attribute_group attr_grp; struct kobj_attribute kobj_attr_cntrl_reg; u32 cntrl_reg; struct kobj_attribute kobj_attr_secs_reg; u32 secs_reg; struct kobj_attribute kobj_attr_nsecs_reg; u32 nsecs_reg; struct kobj_attribute kobj_attr_pulsecnt_reg; u32 pulsecnt_reg; struct kobj_attribute kobj_attr_clockcnt_reg; u32 clockcnt_reg; struct kobj_attribute kobj_attr_snapshot_reg; u32 snapshot_reg; struct kobj_attribute kobj_attr_reset_time; u32 reset_time; struct kobj_attribute kobj_attr_sfn_reg; u32 sfn_reg; }; /* * Some rudimentary internal state... */ static int pp2s_irq; static struct pp2s_sysfs_t pp2s_sysfs; static struct workqueue_struct *workqueue; static struct work_struct bottom_work; static struct wc_sysfs_t wc_sysfs; static int time_is_set = -1; /* * The following will be set to point at the two regions where the * wall clock registers in question live. * * More specifically: * * *time_bank : Will be used to point at the region which holds two * time registers (ie. the base time or "epoch" when * the time was set), and * * *cntrl_bank1 : A set of registers used to make current wall clock * time calculations relative to the base time... * * *cntrl_bank2 : Similar to cntrl_bank1 */ static void *time_bank; static void *cntrl_bank1; static void *cntrl_bank2; /* * The following function takes GPS time from the FTM and converts it * to unix time. * * OF NOTE: * * Upon hard sync, TFCS seeds the FTM time registers used by this * function with GPS time. * * GPS time is stored in the registers in the following way: * * 1) Two registers, referred to as the base time registers, are * seeded with the time that the first PP2S arrived. These * registers never change value once set. * * 2) Two other register, "pulse count" and "snap shot," hold * dynamic values that, when combined with the base time, yield * the actual GPS time. * * The "pulse count" count register holds the number of PP2S pulses * that have transpired prior to the register being read. The "snap * shot" register is a latch register whose value is set by simply * doing a read of the "pulse count" register. The value being * latched is that of the "clock register" which is a free running * clock that's running at 122.88 MHz. Its value goes back to 0 and * free runs again on every PP2S pusle. * * Given the above, we initally calculate GPS time which then gets * converted to unix time thusly: */ static void gps_to_unix_time( struct timespec *ts_ptr) { if (ts_ptr) { u32 pulses = readl_relaxed(WC_PULSECNT_ADDR); u32 clkCnt = readl_relaxed(WC_SNAPSHOT_ADDR); u32 secs = readl_relaxed(WC_SECS_ADDR); u32 nsecs = readl_relaxed(WC_NSECS_ADDR); u32 leaps; u32 rmndr, unused; u64 q; /* * NOTE: * * There is some bit manipulation of the nano second value * below. This is because the nsecs register is doing duel * duty. It not only stores nano seconds, but also, the first * BITS_FOR_LEAP low order bits are used to store leap * seconds. * The remaining high order bits hold a scaled (by a factor of * 16) version of nano seconds. */ leaps = nsecs & ((1 << BITS_FOR_LEAP) - 1); nsecs >>= BITS_FOR_LEAP; nsecs <<= 4; /* * Convert PP2S pulses to seconds and add that in. */ secs += (pulses * 2); /* * Now convert the free running clock into seconds and * nanoseconds * and add that in as well... */ secs += (u32) div_u64_rem((u64) clkCnt, CLK_RATE, &rmndr); q = div_u64_rem((u64) NSEC_PER_SEC, CLK_RATE, &unused); nsecs += (u32) ((u64) rmndr * q); /* * And finally, the actual conversion from GPS to unix time... */ ts_ptr->tv_sec = secs + (UNIX_EPOCH_TO_GPS_EPOCH_GAP - leaps); ts_ptr->tv_nsec = nsecs; if (ts_ptr->tv_sec < 0 || ts_ptr->tv_nsec < 0) { ts_ptr->tv_sec = 0; ts_ptr->tv_nsec = 0; } } } /* * Given a register address and a storage location address, this * routine will read a value from the register address and store it at * the location address. */ static ssize_t register_read( const char *caller_ptr, void *reg_addr, char *valfromreg_ptr) { u32 val = readl_relaxed(reg_addr); val = scnprintf(valfromreg_ptr, PAGE_SIZE, "%u", val); LOG_DRVR_DEBUG( "%s -> read %s (from addr %p) with %u bytes\n", caller_ptr, valfromreg_ptr, reg_addr, val); return val; } /* * Given a register address and an address of a value, this routine * will take the value and copy it to the register address... */ static ssize_t register_write( const char *caller_ptr, void *reg_addr, const char *valtoreg_ptr) { u32 val = 0; sscanf(valtoreg_ptr, "%u", &val); writel_relaxed(val, reg_addr); LOG_DRVR_DEBUG( "%s -> wrote %u (to addr %p) with %u bytes\n", caller_ptr, val, reg_addr, sizeof(val)); return strlen(valtoreg_ptr); } /* * The sysfs routine which is called when an application issues a read * on the cntrl_reg sysfs node... */ static ssize_t read_cntrl_reg( struct kobject *kobj, struct kobj_attribute *attr, char *buf) { LOG_DRVR_DEBUG("Entering %s\n", __func__); return register_read(__func__, WC_CONTROL_ADDR, buf); } /* * The sysfs routine which is called when an application issues a read * on the secs_reg sysfs node... */ static ssize_t read_secs_reg( struct kobject *kobj, struct kobj_attribute *attr, char *buf) { LOG_DRVR_DEBUG("Entering %s\n", __func__); return register_read(__func__, WC_SECS_ADDR, buf); } /* * The sysfs routine which is called when an application issues a * write on the secs_reg sysfs node... */ static ssize_t write_secs_reg( struct kobject *kobj, struct kobj_attribute *attr, const char *buf, size_t count) { LOG_DRVR_DEBUG("Entering %s\n", __func__); return register_write(__func__, WC_SECS_ADDR, buf); } /* * The sysfs routine which is called when an application issues a read * on the nsecs_reg sysfs node... */ static ssize_t read_nsecs_reg( struct kobject *kobj, struct kobj_attribute *attr, char *buf) { LOG_DRVR_DEBUG("Entering %s\n", __func__); return register_read(__func__, WC_NSECS_ADDR, buf); } /* * The sysfs routine which is called when an application issues a * write on the nsecs_reg sysfs node... */ static ssize_t write_nsecs_reg( struct kobject *kobj, struct kobj_attribute *attr, const char *buf, size_t count) { LOG_DRVR_DEBUG("Entering %s\n", __func__); return register_write(__func__, WC_NSECS_ADDR, buf); } /* * The sysfs routine which is called when an application issues a read * on the pulsecnt_reg sysfs node... */ static ssize_t read_pulsecnt_reg( struct kobject *kobj, struct kobj_attribute *attr, char *buf) { LOG_DRVR_DEBUG("Entering %s\n", __func__); return register_read(__func__, WC_PULSECNT_ADDR, buf); } /* * The sysfs routine which is called when an application issues a read * on the clockcnt_reg sysfs node... */ static ssize_t read_clockcnt_reg( struct kobject *kobj, struct kobj_attribute *attr, char *buf) { LOG_DRVR_DEBUG("Entering %s\n", __func__); return register_read(__func__, WC_CLOCKCNT_ADDR, buf); } /* * The sysfs routine which is called when an application issues a read * on the snapshot_reg sysfs node... */ static ssize_t read_snapshot_reg( struct kobject *kobj, struct kobj_attribute *attr, char *buf) { LOG_DRVR_DEBUG("Entering %s\n", __func__); return register_read(__func__, WC_SNAPSHOT_ADDR, buf); } /* * The sysfs routine which is called when an application issues a read * on the reset_time sysfs node... */ static ssize_t read_reset_time( struct kobject *kobj, struct kobj_attribute *attr, char *buf) { LOG_DRVR_DEBUG("Entering %s\n", __func__); return scnprintf(buf, PAGE_SIZE, "linux time is %s", (time_is_set > 0) ? "set" : "not set"); } /* * The sysfs routine which is called when an application issues a * write on the reset_time sysfs node... */ static ssize_t write_reset_time( struct kobject *kobj, struct kobj_attribute *attr, const char *buf, size_t count) { LOG_DRVR_DEBUG("Entering %s\n", __func__); time_is_set = -1; return 1; } /* * The sysfs routine which is called when an application issues a read * on the sfn_reg sysfs node... */ static ssize_t read_sfn_reg( struct kobject *kobj, struct kobj_attribute *attr, char *buf) { LOG_DRVR_DEBUG("Entering %s\n", __func__); return register_read(__func__, WC_SFN_ADDR, buf); } /* * The sysfs routine which is called when an application issues a * write on the sfn_reg sysfs node... */ static ssize_t write_sfn_reg( struct kobject *kobj, struct kobj_attribute *attr, const char *buf, size_t count) { LOG_DRVR_DEBUG("Entering %s\n", __func__); return register_write(__func__, WC_SFN_ADDR, buf); } /* * The sysfs routine which is called when an application issues a read * on the pp2s_cnt sysfs node... */ static ssize_t read_pp2s_cnt( struct kobject *kobj, struct kobj_attribute *attr, char *buf) { LOG_DRVR_DEBUG("Entering %s\n", __func__); return read_pulsecnt_reg(kobj, attr, buf); } /* * The pp2s interrupt handler top half schedules the following to be * run upon reception of the pp2s interrupt, so that any polls or * selects on the pp2s_cnt sysfs node can be released... */ static void pp2s_intr_bottom( struct work_struct *work) { LOG_DRVR_DEBUG("Entering %s\n", __func__); /* * On the first PP2S interrupt, GPS time will be set in the FTM * and we'll have the first pass through this function. At that * point, it is required to take GPS time, convert it to unix * time, and to set it in the kernel, hence... */ if (time_is_set <= 0) { struct timespec ts; gps_to_unix_time(&ts); time_is_set = !do_settimeofday(&ts); if (!time_is_set) LOG_DRVR_ERR("do_settimeofday() failed\n"); } RELEASE_SELECT_ON_ATTR(&pp2s_sysfs, pp2s_cnt); } /* * The following is called upon receipt of the pp2s interrupt. It * simply schedules pp2s_intr_bottom to be called... */ static irqreturn_t pp2s_intr_top( int irq, void *arbDatPtr) { LOG_DRVR_DEBUG("Entering %s\n", __func__); queue_work(workqueue, &bottom_work); pp2s_sysfs.pp2s_cnt++; return IRQ_HANDLED; } /* * The following will probe the device tree file for this driver's IRQ * number. Once found, it will be enabled after a top half interrupt * handler is associated with the IRQ. * * This routine will also create the pp2s_cnt sysfs node... */ static int pp2s_probe( struct platform_device *pdev) { struct resource *r; int ret = 0; LOG_DRVR_DEBUG("Entering %s\n", __func__); r = platform_get_resource_byname(pdev, IORESOURCE_IRQ, "pp2s_irq"); if (r == 0) { LOG_DRVR_ERR( "%s: platform_get_resource_byname() failed\n", __func__); ret = -ENODEV; goto perr0; } pp2s_irq = r->start; LOG_DRVR_DEBUG( "%s: retrieved irq (%d) from device tree\n", __func__, pp2s_irq); ret = request_irq( pp2s_irq, pp2s_intr_top, IRQF_TRIGGER_RISING, PP2S_MODULE_NAME, 0); if (ret != 0) { LOG_DRVR_ERR( "%s: request_irq() failed\n", __func__); goto perr0; } /* * Create the sysfs obj... */ pp2s_sysfs.kobj = kobject_create_and_add("pp2s", kernel_kobj); if (pp2s_sysfs.kobj == 0) { ret = -ENOMEM; goto perr1; } /* * Allocate space for sysfs attrs... */ pp2s_sysfs.attr_grp.attrs = kzalloc(ATTR_PTR_SIZE * NUM_PP2S_SYSFS_REG_NODES, GFP_KERNEL); if (pp2s_sysfs.attr_grp.attrs == 0) { ret = -ENOMEM; goto perr2; } /* * Add sysfs attr... */ MAKE_RO_ATTR(pp2s_cnt, &pp2s_sysfs, 0); /* * Create sysfs group... */ ret = sysfs_create_group(pp2s_sysfs.kobj, &pp2s_sysfs.attr_grp); if (ret != 0) goto perr3; return ret; perr3: kzfree(pp2s_sysfs.attr_grp.attrs); pp2s_sysfs.attr_grp.attrs = 0; perr2: kobject_put(pp2s_sysfs.kobj); pp2s_sysfs.kobj = 0; perr1: disable_irq(pp2s_irq); free_irq(pp2s_irq, 0); perr0: return ret; } /* * This routine will read the device tree file and initialize a few * important wallclock data structures... */ static int wc_probe( struct platform_device *pdev) { struct resource *r; int ret = 0; LOG_DRVR_DEBUG("Entering %s\n", __func__); /* * Find the address of the time bank and map it in... */ r = platform_get_resource_byname( pdev, IORESOURCE_MEM, "wallclock_time_bank"); if (r == 0) { LOG_DRVR_ERR( "%s: platform_get_resource_byname(time_bank) failed\n", __func__); ret = -ENODEV; goto werr0; } LOG_DRVR_DEBUG( "%s: time_bank start(%d) size(%d)\n", __func__, r->start, resource_size(r)); time_bank = ioremap_nocache(r->start, resource_size(r)); if (time_bank == 0) { LOG_DRVR_ERR( "%s: ioremap(time_bank) failed\n", __func__); ret = -ENOMEM; goto werr0; } /* * Find the address of the 1st control bank and map it in... */ r = platform_get_resource_byname( pdev, IORESOURCE_MEM, "wallclock_cntrl_bank1"); if (r == 0) { LOG_DRVR_ERR( "%s: platform_get_resource_byname(cntrl_bank1) failed\n", __func__); ret = -ENODEV; goto werr1; } LOG_DRVR_DEBUG( "%s: cntrl_bank1 start(%d) size(%d)\n", __func__, r->start, resource_size(r)); cntrl_bank1 = ioremap_nocache(r->start, resource_size(r)); if (cntrl_bank1 == 0) { LOG_DRVR_ERR( "%s: ioremap(cntrl_bank1) failed\n", __func__); ret = -ENOMEM; goto werr1; } /* * Find the address of the 2nd control bank and map it in... */ r = platform_get_resource_byname( pdev, IORESOURCE_MEM, "wallclock_cntrl_bank2"); if (r == 0) { LOG_DRVR_ERR( "%s: platform_get_resource_byname(cntrl_bank2) failed\n", __func__); ret = -ENODEV; goto werr2; } LOG_DRVR_DEBUG( "%s: cntrl_bank2 start(%d) size(%d)\n", __func__, r->start, resource_size(r)); cntrl_bank2 = ioremap_nocache(r->start, resource_size(r)); if (cntrl_bank2 == 0) { LOG_DRVR_ERR( "%s: ioremap(cntrl_bank2) failed\n", __func__); ret = -ENOMEM; goto werr2; } /* * Create the sysfs obj... */ wc_sysfs.kobj = kobject_create_and_add("wall_clock", kernel_kobj); if (wc_sysfs.kobj == 0) { ret = -ENOMEM; goto werr3; } /* * Allocate space for sysfs attrs... */ wc_sysfs.attr_grp.attrs = kzalloc(ATTR_PTR_SIZE * NUM_WC_SYSFS_REG_NODES, GFP_KERNEL); if (wc_sysfs.attr_grp.attrs == 0) { ret = -ENOMEM; goto werr4; } /* * Add sysfs attrs... */ MAKE_RO_ATTR(clockcnt_reg, &wc_sysfs, 0); MAKE_RO_ATTR(cntrl_reg, &wc_sysfs, 1); MAKE_RO_ATTR(pulsecnt_reg, &wc_sysfs, 2); MAKE_RO_ATTR(snapshot_reg, &wc_sysfs, 3); MAKE_RW_ATTR(secs_reg, &wc_sysfs, 4); MAKE_RW_ATTR(nsecs_reg, &wc_sysfs, 5); MAKE_RW_ATTR(reset_time, &wc_sysfs, 6); MAKE_RW_ATTR(sfn_reg, &wc_sysfs, 7); /* * Create sysfs group... */ ret = sysfs_create_group(wc_sysfs.kobj, &wc_sysfs.attr_grp); if (ret != 0) goto werr5; return ret; werr5: kzfree(wc_sysfs.attr_grp.attrs); wc_sysfs.attr_grp.attrs = 0; werr4: kobject_put(wc_sysfs.kobj); wc_sysfs.kobj = 0; werr3: iounmap(cntrl_bank2); cntrl_bank2 = 0; werr2: iounmap(cntrl_bank1); cntrl_bank1 = 0; werr1: iounmap(time_bank); time_bank = 0; werr0: return ret; } /* * Free up pp2s resources upon driver removal. */ static int pp2s_remove( struct platform_device *pdev) { LOG_DRVR_DEBUG("Entering %s\n", __func__); kzfree(pp2s_sysfs.attr_grp.attrs); pp2s_sysfs.attr_grp.attrs = 0; kobject_put(pp2s_sysfs.kobj); pp2s_sysfs.kobj = 0; disable_irq(pp2s_irq); free_irq(pp2s_irq, 0); return 0; } /* * Free up wallclock resources upon driver removal. */ static int wc_remove( struct platform_device *pdev) { LOG_DRVR_DEBUG("Entering %s\n", __func__); kzfree(wc_sysfs.attr_grp.attrs); wc_sysfs.attr_grp.attrs = 0; kobject_put(wc_sysfs.kobj); wc_sysfs.kobj = 0; iounmap(time_bank); time_bank = 0; iounmap(cntrl_bank1); cntrl_bank1 = 0; iounmap(cntrl_bank2); cntrl_bank2 = 0; return 0; } /* * All of the following are standard platform driver initialization * calls... */ static struct of_device_id pp2s_match_table[] = { { .compatible = "qcom,pp2s", }, {} }; static struct platform_driver pp2s_driver = { .probe = pp2s_probe, .remove = pp2s_remove, .driver = { .name = PP2S_MODULE_NAME, .owner = THIS_MODULE, .of_match_table = pp2s_match_table, }, }; static struct of_device_id wc_match_table[] = { { .compatible = "qcom,wallclock", }, {} }; static struct platform_driver wc_driver = { .probe = wc_probe, .remove = wc_remove, .driver = { .name = WC_MODULE_NAME, .owner = THIS_MODULE, .of_match_table = wc_match_table, }, }; /* * Called when device driver module loaded... */ static int __init pp2s_init(void) { int ret; LOG_DRVR_DEBUG( "%s: PP2S interrupt notifier initialization\n", __func__); memset(&pp2s_sysfs, 0, sizeof(pp2s_sysfs)); workqueue = create_singlethread_workqueue("pp2s_wq"); if (workqueue == 0) { LOG_DRVR_ERR( "%s: create_singlethread_workqueue() failed\n", __func__); return -ENOMEM; } INIT_WORK(&bottom_work, pp2s_intr_bottom); ret = platform_driver_register(&pp2s_driver); if (ret) { LOG_DRVR_ERR( "%s: Registration of %s failed\n", __func__, PP2S_MODULE_NAME); return ret; } ret = platform_driver_register(&wc_driver); if (ret) { LOG_DRVR_ERR( "%s: Registration of %s failed\n", __func__, WC_MODULE_NAME); return ret; } return 0; } /* * Called when device driver module unloaded... */ static void __exit pp2s_exit(void) { LOG_DRVR_DEBUG( "%s: PP2S interrupt notifier de-initialization\n", __func__); platform_driver_unregister(&pp2s_driver); platform_driver_unregister(&wc_driver); } MODULE_DESCRIPTION("FSM PP2S Interrupt Notification Driver"); module_init(pp2s_init); module_exit(pp2s_exit);