12d71d8deSAmit Kucheria // SPDX-License-Identifier: GPL-2.0 29066073cSRajendra Nayak /* 39066073cSRajendra Nayak * Copyright (c) 2015, The Linux Foundation. All rights reserved. 49066073cSRajendra Nayak */ 59066073cSRajendra Nayak 67c938f48SAmit Kucheria #include <linux/debugfs.h> 79066073cSRajendra Nayak #include <linux/err.h> 89066073cSRajendra Nayak #include <linux/module.h> 99066073cSRajendra Nayak #include <linux/of.h> 10634e11d5SAmit Kucheria #include <linux/of_platform.h> 119066073cSRajendra Nayak #include <linux/platform_device.h> 129066073cSRajendra Nayak #include <linux/pm.h> 139066073cSRajendra Nayak #include <linux/slab.h> 149066073cSRajendra Nayak #include <linux/thermal.h> 159066073cSRajendra Nayak #include "tsens.h" 169066073cSRajendra Nayak 179066073cSRajendra Nayak static int tsens_get_temp(void *data, int *temp) 189066073cSRajendra Nayak { 198b71bce4SAmit Kucheria struct tsens_sensor *s = data; 2069b628acSAmit Kucheria struct tsens_priv *priv = s->priv; 219066073cSRajendra Nayak 228b71bce4SAmit Kucheria return priv->ops->get_temp(s, temp); 239066073cSRajendra Nayak } 249066073cSRajendra Nayak 252cbcd2eaSAmit Kucheria static int tsens_get_trend(void *data, int trip, enum thermal_trend *trend) 269066073cSRajendra Nayak { 278b71bce4SAmit Kucheria struct tsens_sensor *s = data; 2869b628acSAmit Kucheria struct tsens_priv *priv = s->priv; 299066073cSRajendra Nayak 3069b628acSAmit Kucheria if (priv->ops->get_trend) 318b71bce4SAmit Kucheria return priv->ops->get_trend(s, trend); 329066073cSRajendra Nayak 339066073cSRajendra Nayak return -ENOTSUPP; 349066073cSRajendra Nayak } 359066073cSRajendra Nayak 365b97469aSArnd Bergmann static int __maybe_unused tsens_suspend(struct device *dev) 379066073cSRajendra Nayak { 3869b628acSAmit Kucheria struct tsens_priv *priv = dev_get_drvdata(dev); 399066073cSRajendra Nayak 4069b628acSAmit Kucheria if (priv->ops && priv->ops->suspend) 4169b628acSAmit Kucheria return priv->ops->suspend(priv); 429066073cSRajendra Nayak 439066073cSRajendra Nayak return 0; 449066073cSRajendra Nayak } 459066073cSRajendra Nayak 465b97469aSArnd Bergmann static int __maybe_unused tsens_resume(struct device *dev) 479066073cSRajendra Nayak { 4869b628acSAmit Kucheria struct tsens_priv *priv = dev_get_drvdata(dev); 499066073cSRajendra Nayak 5069b628acSAmit Kucheria if (priv->ops && priv->ops->resume) 5169b628acSAmit Kucheria return priv->ops->resume(priv); 529066073cSRajendra Nayak 539066073cSRajendra Nayak return 0; 549066073cSRajendra Nayak } 559066073cSRajendra Nayak 569066073cSRajendra Nayak static SIMPLE_DEV_PM_OPS(tsens_pm_ops, tsens_suspend, tsens_resume); 579066073cSRajendra Nayak 589066073cSRajendra Nayak static const struct of_device_id tsens_table[] = { 599066073cSRajendra Nayak { 609066073cSRajendra Nayak .compatible = "qcom,msm8916-tsens", 61840a5bd3SRajendra Nayak .data = &data_8916, 629066073cSRajendra Nayak }, { 639066073cSRajendra Nayak .compatible = "qcom,msm8974-tsens", 645e6703bdSRajendra Nayak .data = &data_8974, 65d059c739SRajendra Nayak }, { 66*0e580290SAngeloGioacchino Del Regno .compatible = "qcom,msm8976-tsens", 67*0e580290SAngeloGioacchino Del Regno .data = &data_8976, 68*0e580290SAngeloGioacchino Del Regno }, { 69d059c739SRajendra Nayak .compatible = "qcom,msm8996-tsens", 70d059c739SRajendra Nayak .data = &data_8996, 71191dc74bSAmit Kucheria }, { 72e8c24c6fSAmit Kucheria .compatible = "qcom,tsens-v1", 73e8c24c6fSAmit Kucheria .data = &data_tsens_v1, 74e8c24c6fSAmit Kucheria }, { 75191dc74bSAmit Kucheria .compatible = "qcom,tsens-v2", 76191dc74bSAmit Kucheria .data = &data_tsens_v2, 779066073cSRajendra Nayak }, 789066073cSRajendra Nayak {} 799066073cSRajendra Nayak }; 809066073cSRajendra Nayak MODULE_DEVICE_TABLE(of, tsens_table); 819066073cSRajendra Nayak 829066073cSRajendra Nayak static const struct thermal_zone_of_device_ops tsens_of_ops = { 839066073cSRajendra Nayak .get_temp = tsens_get_temp, 849066073cSRajendra Nayak .get_trend = tsens_get_trend, 85634e11d5SAmit Kucheria .set_trips = tsens_set_trips, 869066073cSRajendra Nayak }; 879066073cSRajendra Nayak 8869b628acSAmit Kucheria static int tsens_register(struct tsens_priv *priv) 899066073cSRajendra Nayak { 90634e11d5SAmit Kucheria int i, ret, irq; 919066073cSRajendra Nayak struct thermal_zone_device *tzd; 92634e11d5SAmit Kucheria struct platform_device *pdev; 939066073cSRajendra Nayak 9469b628acSAmit Kucheria for (i = 0; i < priv->num_sensors; i++) { 9569b628acSAmit Kucheria priv->sensor[i].priv = priv; 968b71bce4SAmit Kucheria tzd = devm_thermal_zone_of_sensor_register(priv->dev, priv->sensor[i].hw_id, 9769b628acSAmit Kucheria &priv->sensor[i], 989066073cSRajendra Nayak &tsens_of_ops); 999066073cSRajendra Nayak if (IS_ERR(tzd)) 1009066073cSRajendra Nayak continue; 10169b628acSAmit Kucheria priv->sensor[i].tzd = tzd; 10269b628acSAmit Kucheria if (priv->ops->enable) 10369b628acSAmit Kucheria priv->ops->enable(priv, i); 1049066073cSRajendra Nayak } 105634e11d5SAmit Kucheria 106634e11d5SAmit Kucheria pdev = of_find_device_by_node(priv->dev->of_node); 107634e11d5SAmit Kucheria if (!pdev) 108634e11d5SAmit Kucheria return -ENODEV; 109634e11d5SAmit Kucheria 110634e11d5SAmit Kucheria irq = platform_get_irq_byname(pdev, "uplow"); 111634e11d5SAmit Kucheria if (irq < 0) { 112634e11d5SAmit Kucheria ret = irq; 113634e11d5SAmit Kucheria goto err_put_device; 114634e11d5SAmit Kucheria } 115634e11d5SAmit Kucheria 116634e11d5SAmit Kucheria ret = devm_request_threaded_irq(&pdev->dev, irq, 117634e11d5SAmit Kucheria NULL, tsens_irq_thread, 118634e11d5SAmit Kucheria IRQF_TRIGGER_HIGH | IRQF_ONESHOT, 119634e11d5SAmit Kucheria dev_name(&pdev->dev), priv); 120634e11d5SAmit Kucheria if (ret) { 121634e11d5SAmit Kucheria dev_err(&pdev->dev, "%s: failed to get irq\n", __func__); 122634e11d5SAmit Kucheria goto err_put_device; 123634e11d5SAmit Kucheria } 124634e11d5SAmit Kucheria 125634e11d5SAmit Kucheria enable_irq_wake(irq); 126634e11d5SAmit Kucheria 127634e11d5SAmit Kucheria err_put_device: 128634e11d5SAmit Kucheria put_device(&pdev->dev); 129634e11d5SAmit Kucheria return ret; 1309066073cSRajendra Nayak } 1319066073cSRajendra Nayak 1329066073cSRajendra Nayak static int tsens_probe(struct platform_device *pdev) 1339066073cSRajendra Nayak { 1349066073cSRajendra Nayak int ret, i; 1359066073cSRajendra Nayak struct device *dev; 1369066073cSRajendra Nayak struct device_node *np; 13769b628acSAmit Kucheria struct tsens_priv *priv; 1383c040ce0SAmit Kucheria const struct tsens_plat_data *data; 1399066073cSRajendra Nayak const struct of_device_id *id; 1406d7c70d1SBjorn Andersson u32 num_sensors; 1419066073cSRajendra Nayak 1429066073cSRajendra Nayak if (pdev->dev.of_node) 1439066073cSRajendra Nayak dev = &pdev->dev; 1449066073cSRajendra Nayak else 1459066073cSRajendra Nayak dev = pdev->dev.parent; 1469066073cSRajendra Nayak 1479066073cSRajendra Nayak np = dev->of_node; 1489066073cSRajendra Nayak 1499066073cSRajendra Nayak id = of_match_node(tsens_table, np); 15020d4fd84SRajendra Nayak if (id) 1519066073cSRajendra Nayak data = id->data; 15220d4fd84SRajendra Nayak else 15320d4fd84SRajendra Nayak data = &data_8960; 1549066073cSRajendra Nayak 1556d7c70d1SBjorn Andersson num_sensors = data->num_sensors; 1566d7c70d1SBjorn Andersson 1576d7c70d1SBjorn Andersson if (np) 1586d7c70d1SBjorn Andersson of_property_read_u32(np, "#qcom,sensors", &num_sensors); 1596d7c70d1SBjorn Andersson 1606d7c70d1SBjorn Andersson if (num_sensors <= 0) { 1613795ad5eSAmit Kucheria dev_err(dev, "%s: invalid number of sensors\n", __func__); 1629066073cSRajendra Nayak return -EINVAL; 1639066073cSRajendra Nayak } 1649066073cSRajendra Nayak 16569b628acSAmit Kucheria priv = devm_kzalloc(dev, 16669b628acSAmit Kucheria struct_size(priv, sensor, num_sensors), 1670ed2dd03SKees Cook GFP_KERNEL); 16869b628acSAmit Kucheria if (!priv) 1699066073cSRajendra Nayak return -ENOMEM; 1709066073cSRajendra Nayak 17169b628acSAmit Kucheria priv->dev = dev; 17269b628acSAmit Kucheria priv->num_sensors = num_sensors; 17369b628acSAmit Kucheria priv->ops = data->ops; 17469b628acSAmit Kucheria for (i = 0; i < priv->num_sensors; i++) { 1759066073cSRajendra Nayak if (data->hw_ids) 17669b628acSAmit Kucheria priv->sensor[i].hw_id = data->hw_ids[i]; 1779066073cSRajendra Nayak else 17869b628acSAmit Kucheria priv->sensor[i].hw_id = i; 1799066073cSRajendra Nayak } 180c1997054SAmit Kucheria priv->feat = data->feat; 181c1997054SAmit Kucheria priv->fields = data->fields; 1829066073cSRajendra Nayak 1830e9c0bc7SAmit Kucheria platform_set_drvdata(pdev, priv); 1840e9c0bc7SAmit Kucheria 18569b628acSAmit Kucheria if (!priv->ops || !priv->ops->init || !priv->ops->get_temp) 1869066073cSRajendra Nayak return -EINVAL; 1879066073cSRajendra Nayak 18869b628acSAmit Kucheria ret = priv->ops->init(priv); 1899066073cSRajendra Nayak if (ret < 0) { 1903795ad5eSAmit Kucheria dev_err(dev, "%s: init failed\n", __func__); 1919066073cSRajendra Nayak return ret; 1929066073cSRajendra Nayak } 1939066073cSRajendra Nayak 19469b628acSAmit Kucheria if (priv->ops->calibrate) { 19569b628acSAmit Kucheria ret = priv->ops->calibrate(priv); 1969066073cSRajendra Nayak if (ret < 0) { 197fc7d18cfSAmit Kucheria if (ret != -EPROBE_DEFER) 1983795ad5eSAmit Kucheria dev_err(dev, "%s: calibration failed\n", __func__); 1999066073cSRajendra Nayak return ret; 2009066073cSRajendra Nayak } 2019066073cSRajendra Nayak } 2029066073cSRajendra Nayak 2030e9c0bc7SAmit Kucheria return tsens_register(priv); 2049066073cSRajendra Nayak } 2059066073cSRajendra Nayak 2069066073cSRajendra Nayak static int tsens_remove(struct platform_device *pdev) 2079066073cSRajendra Nayak { 20869b628acSAmit Kucheria struct tsens_priv *priv = platform_get_drvdata(pdev); 2099066073cSRajendra Nayak 2107c938f48SAmit Kucheria debugfs_remove_recursive(priv->debug_root); 211634e11d5SAmit Kucheria tsens_disable_irq(priv); 21269b628acSAmit Kucheria if (priv->ops->disable) 21369b628acSAmit Kucheria priv->ops->disable(priv); 2149066073cSRajendra Nayak 2159066073cSRajendra Nayak return 0; 2169066073cSRajendra Nayak } 2179066073cSRajendra Nayak 2189066073cSRajendra Nayak static struct platform_driver tsens_driver = { 2199066073cSRajendra Nayak .probe = tsens_probe, 2209066073cSRajendra Nayak .remove = tsens_remove, 2219066073cSRajendra Nayak .driver = { 2229066073cSRajendra Nayak .name = "qcom-tsens", 2239066073cSRajendra Nayak .pm = &tsens_pm_ops, 2249066073cSRajendra Nayak .of_match_table = tsens_table, 2259066073cSRajendra Nayak }, 2269066073cSRajendra Nayak }; 2279066073cSRajendra Nayak module_platform_driver(tsens_driver); 2289066073cSRajendra Nayak 2299066073cSRajendra Nayak MODULE_LICENSE("GPL v2"); 2309066073cSRajendra Nayak MODULE_DESCRIPTION("QCOM Temperature Sensor driver"); 2319066073cSRajendra Nayak MODULE_ALIAS("platform:qcom-tsens"); 232