diff --git a/Documentation/devicetree/bindings/qdsp/msm-ssc-sensors.txt b/Documentation/devicetree/bindings/qdsp/msm-ssc-sensors.txt new file mode 100644 index 000000000000..165153dde994 --- /dev/null +++ b/Documentation/devicetree/bindings/qdsp/msm-ssc-sensors.txt @@ -0,0 +1,11 @@ +* msm-ssc-sensors + +Required properties: + + - compatible: "qcom,msm-ssc-sensors" + +Example: + + qcom,msm-ssc-sensors { + compatible = "qcom,msm-ssc-sensors"; + }; diff --git a/drivers/Kconfig b/drivers/Kconfig index 66757e1f8047..bcd93ab5eebe 100644 --- a/drivers/Kconfig +++ b/drivers/Kconfig @@ -202,4 +202,6 @@ source "drivers/fpga/Kconfig" source "drivers/bif/Kconfig" +source "drivers/sensors/Kconfig" + endmenu diff --git a/drivers/Makefile b/drivers/Makefile index 018d6cbd4178..fafad11c676b 100644 --- a/drivers/Makefile +++ b/drivers/Makefile @@ -175,3 +175,5 @@ obj-$(CONFIG_NVMEM) += nvmem/ obj-$(CONFIG_FPGA) += fpga/ obj-$(CONFIG_BIF) += bif/ + +obj-$(CONFIG_SENSORS_SSC) += sensors/ diff --git a/drivers/sensors/Kconfig b/drivers/sensors/Kconfig new file mode 100644 index 000000000000..0e2da7967883 --- /dev/null +++ b/drivers/sensors/Kconfig @@ -0,0 +1,6 @@ +config SENSORS_SSC + bool "Enable Sensors Driver Support for SSC" + help + Add support for sensors SSC driver. + This driver is used for exercising sensors use case, + time syncing with ADSP clock. diff --git a/drivers/sensors/Makefile b/drivers/sensors/Makefile new file mode 100644 index 000000000000..08d8a6399313 --- /dev/null +++ b/drivers/sensors/Makefile @@ -0,0 +1 @@ +obj-$(CONFIG_SENSORS_SSC) += sensors_ssc.o diff --git a/drivers/sensors/sensors_ssc.c b/drivers/sensors/sensors_ssc.c new file mode 100644 index 000000000000..580f6ba8dda8 --- /dev/null +++ b/drivers/sensors/sensors_ssc.c @@ -0,0 +1,396 @@ +/* Copyright (c) 2012-2015, 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 +#include + +#include +#include +#include +#include +#include + +#define IMAGE_LOAD_CMD 1 +#define IMAGE_UNLOAD_CMD 0 +#define CLASS_NAME "ssc" +#define DRV_NAME "sensors" +#define DRV_VERSION "2.00" +#ifdef CONFIG_COMPAT +#define DSPS_IOCTL_READ_SLOW_TIMER32 _IOR(DSPS_IOCTL_MAGIC, 3, compat_uint_t) +#endif + +struct sns_ssc_control_s { + struct class *dev_class; + dev_t dev_num; + struct device *dev; + struct cdev *cdev; +}; +static struct sns_ssc_control_s sns_ctl; + +static ssize_t slpi_boot_store(struct kobject *kobj, + struct kobj_attribute *attr, + const char *buf, size_t count); + +struct slpi_loader_private { + void *pil_h; + struct kobject *boot_slpi_obj; + struct attribute_group *attr_group; +}; + +static struct kobj_attribute slpi_boot_attribute = + __ATTR(boot, 0220, NULL, slpi_boot_store); + +static struct attribute *attrs[] = { + &slpi_boot_attribute.attr, + NULL, +}; + +static struct platform_device *slpi_private; + +static void slpi_loader_do(struct platform_device *pdev) +{ + + struct slpi_loader_private *priv = NULL; + + if (!pdev) { + dev_err(&pdev->dev, "%s: Platform device null\n", __func__); + goto fail; + } + + if (!pdev->dev.of_node) { + dev_err(&pdev->dev, + "%s: Device tree information missing\n", __func__); + goto fail; + } + + priv = platform_get_drvdata(pdev); + if (!priv) { + dev_err(&pdev->dev, + " %s: Private data get failed\n", __func__); + goto fail; + } + + priv->pil_h = subsystem_get("slpi"); + if (IS_ERR(priv->pil_h)) { + dev_err(&pdev->dev, "%s: pil get failed,\n", + __func__); + goto fail; + } + + dev_err(&pdev->dev, "%s: SLPI image is loaded\n", __func__); + return; + +fail: + dev_err(&pdev->dev, "%s: SLPI image loading failed\n", __func__); +} + +static void slpi_loader_unload(struct platform_device *pdev) +{ + struct slpi_loader_private *priv = NULL; + + priv = platform_get_drvdata(pdev); + + if (!priv) + return; + + if (priv->pil_h) { + dev_dbg(&pdev->dev, "%s: calling subsystem put\n", __func__); + subsystem_put(priv->pil_h); + priv->pil_h = NULL; + } +} + +static ssize_t slpi_boot_store(struct kobject *kobj, + struct kobj_attribute *attr, + const char *buf, + size_t count) +{ + int boot = 0; + + if (sscanf(buf, "%du", &boot) != 1) + return -EINVAL; + + if (boot == IMAGE_LOAD_CMD) { + pr_debug("%s: going to call slpi_loader_do\n", __func__); + slpi_loader_do(slpi_private); + } else if (boot == IMAGE_UNLOAD_CMD) { + pr_debug("%s: going to call slpi_unloader\n", __func__); + slpi_loader_unload(slpi_private); + } + return count; +} + +static int slpi_loader_init_sysfs(struct platform_device *pdev) +{ + int ret = -EINVAL; + struct slpi_loader_private *priv = NULL; + + slpi_private = NULL; + + priv = devm_kzalloc(&pdev->dev, sizeof(*priv), GFP_KERNEL); + if (!priv) { + ret = -ENOMEM; + return ret; + } + + platform_set_drvdata(pdev, priv); + + priv->pil_h = NULL; + priv->boot_slpi_obj = NULL; + priv->attr_group = devm_kzalloc(&pdev->dev, + sizeof(*(priv->attr_group)), + GFP_KERNEL); + if (!priv->attr_group) { + dev_err(&pdev->dev, "%s: malloc attr_group failed\n", + __func__); + ret = -ENOMEM; + goto error_return; + } + + priv->attr_group->attrs = attrs; + + priv->boot_slpi_obj = kobject_create_and_add("boot_slpi", kernel_kobj); + if (!priv->boot_slpi_obj) { + dev_err(&pdev->dev, "%s: sysfs create and add failed\n", + __func__); + ret = -ENOMEM; + goto error_return; + } + + ret = sysfs_create_group(priv->boot_slpi_obj, priv->attr_group); + if (ret) { + dev_err(&pdev->dev, "%s: sysfs create group failed %d\n", + __func__, ret); + goto error_return; + } + + slpi_private = pdev; + + return 0; + +error_return: + + if (priv->boot_slpi_obj) { + kobject_del(priv->boot_slpi_obj); + priv->boot_slpi_obj = NULL; + } + + return ret; +} + +static int slpi_loader_remove(struct platform_device *pdev) +{ + struct slpi_loader_private *priv = NULL; + + priv = platform_get_drvdata(pdev); + + if (!priv) + return 0; + + if (priv->pil_h) { + subsystem_put(priv->pil_h); + priv->pil_h = NULL; + } + + if (priv->boot_slpi_obj) { + sysfs_remove_group(priv->boot_slpi_obj, priv->attr_group); + kobject_del(priv->boot_slpi_obj); + priv->boot_slpi_obj = NULL; + } + + return 0; +} + +/* + * Read QTimer clock ticks and scale down to 32KHz clock as used + * in DSPS + */ +static u32 sns_read_qtimer(void) +{ + u64 val; + val = arch_counter_get_cntpct(); + /* + * To convert ticks from 19.2 Mhz clock to 32768 Hz clock: + * x = (value * 32768) / 19200000 + * This is same as first left shift the value by 4 bits, i.e. mutiply + * by 16, and then divide by 9375. The latter is preferable since + * QTimer tick (value) is 56-bit, so (value * 32768) could overflow, + * while (value * 16) will never do + */ + val <<= 4; + do_div(val, 9375); + + return (u32)val; +} + +static int sensors_ssc_open(struct inode *ip, struct file *fp) +{ + return 0; +} + +static int sensors_ssc_release(struct inode *inode, struct file *file) +{ + return 0; +} + +static long sensors_ssc_ioctl(struct file *file, + unsigned int cmd, unsigned long arg) +{ + int ret = 0; + u32 val = 0; + + switch (cmd) { + case DSPS_IOCTL_READ_SLOW_TIMER: +#ifdef CONFIG_COMPAT + case DSPS_IOCTL_READ_SLOW_TIMER32: +#endif + val = sns_read_qtimer(); + ret = put_user(val, (u32 __user *) arg); + break; + + default: + ret = -EINVAL; + break; + } + + return ret; +} + +const struct file_operations sensors_ssc_fops = { + .owner = THIS_MODULE, + .open = sensors_ssc_open, + .release = sensors_ssc_release, +#ifdef CONFIG_COMPAT + .compat_ioctl = sensors_ssc_ioctl, +#endif + .unlocked_ioctl = sensors_ssc_ioctl +}; + +static int sensors_ssc_probe(struct platform_device *pdev) +{ + int ret = slpi_loader_init_sysfs(pdev); + + if (ret != 0) { + dev_err(&pdev->dev, "%s: Error in initing sysfs\n", __func__); + return ret; + } + + sns_ctl.dev_class = class_create(THIS_MODULE, CLASS_NAME); + if (sns_ctl.dev_class == NULL) { + pr_err("%s: class_create fail.\n", __func__); + goto res_err; + } + + ret = alloc_chrdev_region(&sns_ctl.dev_num, 0, 1, DRV_NAME); + if (ret) { + pr_err("%s: alloc_chrdev_region fail.\n", __func__); + goto alloc_chrdev_region_err; + } + + sns_ctl.dev = device_create(sns_ctl.dev_class, NULL, + sns_ctl.dev_num, + &sns_ctl, DRV_NAME); + if (IS_ERR(sns_ctl.dev)) { + pr_err("%s: device_create fail.\n", __func__); + goto device_create_err; + } + + sns_ctl.cdev = cdev_alloc(); + if (sns_ctl.cdev == NULL) { + pr_err("%s: cdev_alloc fail.\n", __func__); + goto cdev_alloc_err; + } + cdev_init(sns_ctl.cdev, &sensors_ssc_fops); + sns_ctl.cdev->owner = THIS_MODULE; + + ret = cdev_add(sns_ctl.cdev, sns_ctl.dev_num, 1); + if (ret) { + pr_err("%s: cdev_add fail.\n", __func__); + goto cdev_add_err; + } + + return 0; + +cdev_add_err: + kfree(sns_ctl.cdev); +cdev_alloc_err: + device_destroy(sns_ctl.dev_class, sns_ctl.dev_num); +device_create_err: + unregister_chrdev_region(sns_ctl.dev_num, 1); +alloc_chrdev_region_err: + class_destroy(sns_ctl.dev_class); +res_err: + return -ENODEV; +} + +static int sensors_ssc_remove(struct platform_device *pdev) +{ + slpi_loader_remove(pdev); + cdev_del(sns_ctl.cdev); + kfree(sns_ctl.cdev); + sns_ctl.cdev = NULL; + device_destroy(sns_ctl.dev_class, sns_ctl.dev_num); + unregister_chrdev_region(sns_ctl.dev_num, 1); + class_destroy(sns_ctl.dev_class); + + return 0; +} + +static const struct of_device_id msm_ssc_sensors_dt_match[] = { + {.compatible = "qcom,msm-ssc-sensors"}, + {}, +}; +MODULE_DEVICE_TABLE(of, msm_ssc_sensors_dt_match); + +static struct platform_driver sensors_ssc_driver = { + .driver = { + .name = "sensors-ssc", + .owner = THIS_MODULE, + .of_match_table = msm_ssc_sensors_dt_match, + }, + .probe = sensors_ssc_probe, + .remove = sensors_ssc_remove, +}; + +static int __init sensors_ssc_init(void) +{ + int rc; + + pr_debug("%s driver version %s.\n", DRV_NAME, DRV_VERSION); + rc = platform_driver_register(&sensors_ssc_driver); + if (rc) { + pr_err("%s: Failed to register sensors ssc driver\n", + __func__); + return rc; + } + + return 0; +} + +static void __exit sensors_ssc_exit(void) +{ + platform_driver_unregister(&sensors_ssc_driver); +} + +module_init(sensors_ssc_init); +module_exit(sensors_ssc_exit); +MODULE_LICENSE("GPL v2"); +MODULE_DESCRIPTION("Sensors SSC driver"); diff --git a/include/uapi/linux/Kbuild b/include/uapi/linux/Kbuild index a4859e2c673e..50d6bb3ac207 100644 --- a/include/uapi/linux/Kbuild +++ b/include/uapi/linux/Kbuild @@ -470,3 +470,4 @@ header-y += xilinx-v4l2-controls.h header-y += zorro.h header-y += zorro_ids.h header-y += userfaultfd.h +header-y += msm_dsps.h diff --git a/include/uapi/linux/msm_dsps.h b/include/uapi/linux/msm_dsps.h new file mode 100644 index 000000000000..a21927dce368 --- /dev/null +++ b/include/uapi/linux/msm_dsps.h @@ -0,0 +1,16 @@ +#ifndef _UAPI_DSPS_H_ +#define _UAPI_DSPS_H_ + +#include + +#define DSPS_IOCTL_MAGIC 'd' + +#define DSPS_IOCTL_ON _IO(DSPS_IOCTL_MAGIC, 1) +#define DSPS_IOCTL_OFF _IO(DSPS_IOCTL_MAGIC, 2) + +#define DSPS_IOCTL_READ_SLOW_TIMER _IOR(DSPS_IOCTL_MAGIC, 3, unsigned int*) +#define DSPS_IOCTL_READ_FAST_TIMER _IOR(DSPS_IOCTL_MAGIC, 4, unsigned int*) + +#define DSPS_IOCTL_RESET _IO(DSPS_IOCTL_MAGIC, 5) + +#endif /* _UAPI_DSPS_H_ */