diff options
author | Mahesh Sivasubramanian <msivasub@codeaurora.org> | 2016-01-11 14:27:35 -0700 |
---|---|---|
committer | David Keitel <dkeitel@codeaurora.org> | 2016-03-22 11:08:34 -0700 |
commit | b68798fafa4ee121b3f6ce13a3a7ef92b6e960a6 (patch) | |
tree | cd2e81663b67c1e50d60b735f4b868def179aad9 /drivers/thermal/lmh_lite.c | |
parent | 3da2a8c7d197a827076d241fe492cb63736d69ed (diff) |
soc: qcom: Snapshot of thermal/LMH drivers
This snapshot is taken as of msm-3.18 commit e70ad0c (Promotion of
kernel.lnx.3.18-151201.)
Include necessary thermal_core changes to convert long to int inline with
upstream kernel changes.
Change-Id: I642b666518fe72385794b743989a0f5e5120ec03
Conflicts:
drivers/thermal/Makefile
Diffstat (limited to 'drivers/thermal/lmh_lite.c')
-rw-r--r-- | drivers/thermal/lmh_lite.c | 1408 |
1 files changed, 1408 insertions, 0 deletions
diff --git a/drivers/thermal/lmh_lite.c b/drivers/thermal/lmh_lite.c new file mode 100644 index 000000000000..0b2067a5dee1 --- /dev/null +++ b/drivers/thermal/lmh_lite.c @@ -0,0 +1,1408 @@ +/* Copyright (c) 2014-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. + * + */ + +#define pr_fmt(fmt) "%s:%s " fmt, KBUILD_MODNAME, __func__ + +#include <linux/module.h> +#include <linux/platform_device.h> +#include <linux/interrupt.h> +#include <linux/workqueue.h> +#include <linux/kernel.h> +#include <linux/io.h> +#include <linux/err.h> +#include <linux/of.h> +#include <linux/mutex.h> +#include "lmh_interface.h" +#include <linux/slab.h> +#include <asm/cacheflush.h> +#include <soc/qcom/scm.h> +#include <linux/dma-mapping.h> +#include <linux/regulator/consumer.h> + +#define CREATE_TRACE_POINTS +#define TRACE_MSM_LMH +#include <trace/trace_thermal.h> + +#define LMH_DRIVER_NAME "lmh-lite-driver" +#define LMH_INTERRUPT "lmh-interrupt" +#define LMH_DEVICE "lmh-profile" +#define LMH_MAX_SENSOR 10 +#define LMH_GET_PROFILE_SIZE 10 +#define LMH_SCM_PAYLOAD_SIZE 10 +#define LMH_DEFAULT_PROFILE 0 +#define LMH_DEBUG_READ_TYPE 0x0 +#define LMH_DEBUG_CONFIG_TYPE 0x1 +#define LMH_CHANGE_PROFILE 0x01 +#define LMH_GET_PROFILES 0x02 +#define LMH_CTRL_QPMDA 0x03 +#define LMH_TRIM_ERROR 0x04 +#define LMH_GET_INTENSITY 0x06 +#define LMH_GET_SENSORS 0x07 +#define LMH_DEBUG_SET 0x08 +#define LMH_DEBUG_READ_BUF_SIZE 0x09 +#define LMH_DEBUG_READ 0x0A +#define LMH_DEBUG_GET_TYPE 0x0B +#define MAX_TRACE_EVENT_MSG_LEN 50 +#define APCS_DPM_VOLTAGE_SCALE 0x09950804 +#define LMH_ODCM_MAX_COUNT 6 + +#define LMH_CHECK_SCM_CMD(_cmd) \ + do { \ + if (!scm_is_call_available(SCM_SVC_LMH, _cmd)) { \ + pr_err("SCM cmd:%d not available\n", _cmd); \ + return -ENODEV; \ + } \ + } while (0) + +#define LMH_GET_RECURSSIVE_DATA(desc_arg, cmd_idx, cmd_buf, payload, next, \ + size, cmd_id, dest_buf, ret) \ + do { \ + int idx = 0; \ + desc_arg.args[cmd_idx] = cmd_buf.list_start = next; \ + trace_lmh_event_call("GET_TYPE enter"); \ + dmac_flush_range(payload, payload + sizeof(uint32_t) * \ + LMH_SCM_PAYLOAD_SIZE); \ + if (!is_scm_armv8()) { \ + ret = scm_call(SCM_SVC_LMH, cmd_id, \ + (void *) &cmd_buf, SCM_BUFFER_SIZE(cmd_buf), \ + &size, SCM_BUFFER_SIZE(size)); \ + } else { \ + ret = scm_call2(SCM_SIP_FNID(SCM_SVC_LMH, \ + cmd_id), &desc_arg); \ + size = desc_arg.ret[0]; \ + } \ + /* Have barrier before reading from TZ data */ \ + mb(); \ + trace_lmh_event_call("GET_TYPE exit"); \ + if (ret) { \ + pr_err("Error in SCM v%d get type. cmd:%x err:%d\n", \ + (is_scm_armv8()) ? 8 : 7, cmd_id, ret); \ + break; \ + } \ + if (!size) { \ + pr_err("No LMH device supported.\n"); \ + ret = -ENODEV; \ + break; \ + } \ + if (!dest_buf) { \ + dest_buf = devm_kzalloc(lmh_data->dev, \ + sizeof(uint32_t) * size, GFP_KERNEL); \ + if (!dest_buf) { \ + ret = -ENOMEM; \ + break; \ + } \ + } \ + for (idx = next; \ + idx < min((next + LMH_SCM_PAYLOAD_SIZE), size); \ + idx++) \ + dest_buf[idx] = payload[idx - next]; \ + next += LMH_SCM_PAYLOAD_SIZE; \ + } while (next < size) \ + +struct __attribute__((__packed__)) lmh_sensor_info { + uint32_t name; + uint32_t node_id; + uint32_t intensity; + uint32_t max_intensity; + uint32_t type; +}; + +struct __attribute__((__packed__)) lmh_sensor_packet { + uint32_t count; + struct lmh_sensor_info sensor[LMH_MAX_SENSOR]; +}; + +struct lmh_profile { + struct lmh_device_ops dev_ops; + uint32_t level_ct; + uint32_t curr_level; + uint32_t *levels; + uint32_t read_type_count; + uint32_t config_type_count; +}; + +struct lmh_debug { + struct lmh_debug_ops debug_ops; + uint32_t *read_type; + uint32_t *config_type; + uint32_t read_type_count; + uint32_t config_type_count; +}; + +struct lmh_driver_data { + struct device *dev; + struct workqueue_struct *poll_wq; + struct delayed_work poll_work; + uint32_t log_enabled; + uint32_t log_delay; + enum lmh_monitor_state intr_state; + uint32_t intr_reg_val; + uint32_t intr_status_val; + uint32_t trim_err_offset; + bool trim_err_disable; + void *intr_addr; + int irq_num; + int max_sensor_count; + struct lmh_profile dev_info; + struct lmh_debug debug_info; + struct regulator *regulator; + struct notifier_block dpm_notifier_blk; + void __iomem *dpm_voltage_scale_reg; + uint32_t odcm_thresh_mV; + void __iomem *odcm_reg[LMH_ODCM_MAX_COUNT]; + bool odcm_enabled; +}; + +struct lmh_sensor_data { + char sensor_name[LMH_NAME_MAX]; + uint32_t sensor_hw_name; + uint32_t sensor_hw_node_id; + int sensor_sw_id; + struct lmh_sensor_ops ops; + long last_read_value; + struct list_head list_ptr; +}; + +struct lmh_default_data { + uint32_t default_profile; + uint32_t odcm_reg_addr[LMH_ODCM_MAX_COUNT]; +}; + +static struct lmh_default_data lmh_lite_data = { + .default_profile = 0, +}; +static struct lmh_default_data lmh_v1_data = { + .default_profile = 1, + .odcm_reg_addr = { 0x09981030, /* CPU0 */ + 0x09991030, /* CPU1 */ + 0x099A1028, /* APC0_L2 */ + 0x099B1030, /* CPU2 */ + 0x099C1030, /* CPU3 */ + 0x099D1028, /* APC1_l2 */ + }, +}; +static struct lmh_default_data *lmh_hw_data; +static struct lmh_driver_data *lmh_data; +static DECLARE_RWSEM(lmh_sensor_access); +static DEFINE_MUTEX(lmh_sensor_read); +static DEFINE_MUTEX(lmh_odcm_access); +static LIST_HEAD(lmh_sensor_list); + +static int lmh_read(struct lmh_sensor_ops *ops, long *val) +{ + struct lmh_sensor_data *lmh_sensor = container_of(ops, + struct lmh_sensor_data, ops); + + mutex_lock(&lmh_sensor_read); + *val = lmh_sensor->last_read_value; + mutex_unlock(&lmh_sensor_read); + + return 0; +} + +static int lmh_ctrl_qpmda(uint32_t enable) +{ + int ret = 0; + struct scm_desc desc_arg; + struct { + uint32_t enable; + uint32_t rate; + } cmd_buf; + + desc_arg.args[0] = cmd_buf.enable = enable; + desc_arg.args[1] = cmd_buf.rate = lmh_data->log_delay; + desc_arg.arginfo = SCM_ARGS(2, SCM_VAL, SCM_VAL); + trace_lmh_event_call("CTRL_QPMDA enter"); + if (!is_scm_armv8()) + ret = scm_call(SCM_SVC_LMH, LMH_CTRL_QPMDA, + (void *) &cmd_buf, SCM_BUFFER_SIZE(cmd_buf), NULL, 0); + else + ret = scm_call2(SCM_SIP_FNID(SCM_SVC_LMH, + LMH_CTRL_QPMDA), &desc_arg); + trace_lmh_event_call("CTRL_QPMDA exit"); + if (ret) { + pr_err("Error in SCM v%d %s QPMDA call. err:%d\n", + (is_scm_armv8()) ? 8 : 7, (enable) ? "enable" : + "disable", ret); + goto ctrl_exit; + } + +ctrl_exit: + return ret; +} + +static int lmh_disable_log(void) +{ + int ret = 0; + + if (!lmh_data->log_enabled) + return ret; + ret = lmh_ctrl_qpmda(0); + if (ret) + goto disable_exit; + pr_debug("LMH hardware log disabled.\n"); + lmh_data->log_enabled = 0; + +disable_exit: + return ret; +} + +static int lmh_enable_log(uint32_t delay, uint32_t reg_val) +{ + int ret = 0; + + if (lmh_data->log_enabled == reg_val && lmh_data->log_delay == delay) + return ret; + + lmh_data->log_delay = delay; + ret = lmh_ctrl_qpmda(reg_val); + if (ret) + goto enable_exit; + pr_debug("LMH hardware log enabled[%u]. delay:%u\n", reg_val, delay); + lmh_data->log_enabled = reg_val; + +enable_exit: + return ret; +} + +static void lmh_update(struct lmh_driver_data *lmh_dat, + struct lmh_sensor_data *lmh_sensor) +{ + if (lmh_sensor->last_read_value > 0 && !(lmh_dat->intr_status_val + & BIT(lmh_sensor->sensor_sw_id))) { + pr_debug("Sensor:[%s] interrupt triggered\n", + lmh_sensor->sensor_name); + trace_lmh_sensor_interrupt(lmh_sensor->sensor_name, + lmh_sensor->last_read_value); + lmh_dat->intr_status_val |= BIT(lmh_sensor->sensor_sw_id); + } else if (lmh_sensor->last_read_value == 0 && (lmh_dat->intr_status_val + & BIT(lmh_sensor->sensor_sw_id))) { + pr_debug("Sensor:[%s] interrupt clear\n", + lmh_sensor->sensor_name); + trace_lmh_sensor_interrupt(lmh_sensor->sensor_name, + lmh_sensor->last_read_value); + + lmh_data->intr_status_val ^= BIT(lmh_sensor->sensor_sw_id); + } + lmh_sensor->ops.new_value_notify(&lmh_sensor->ops, + lmh_sensor->last_read_value); +} + +static void lmh_read_and_update(struct lmh_driver_data *lmh_dat) +{ + int ret = 0, idx = 0; + struct lmh_sensor_data *lmh_sensor = NULL; + static struct lmh_sensor_packet payload; + struct scm_desc desc_arg; + struct { + /* TZ is 32-bit right now */ + uint32_t addr; + uint32_t size; + } cmd_buf; + + mutex_lock(&lmh_sensor_read); + list_for_each_entry(lmh_sensor, &lmh_sensor_list, list_ptr) + lmh_sensor->last_read_value = 0; + payload.count = 0; + cmd_buf.addr = SCM_BUFFER_PHYS(&payload); + /* &payload may be a physical address > 4 GB */ + desc_arg.args[0] = SCM_BUFFER_PHYS(&payload); + desc_arg.args[1] = cmd_buf.size + = SCM_BUFFER_SIZE(struct lmh_sensor_packet); + desc_arg.arginfo = SCM_ARGS(2, SCM_RW, SCM_VAL); + trace_lmh_event_call("GET_INTENSITY enter"); + dmac_flush_range(&payload, &payload + sizeof(struct lmh_sensor_packet)); + if (!is_scm_armv8()) + ret = scm_call(SCM_SVC_LMH, LMH_GET_INTENSITY, + (void *) &cmd_buf, SCM_BUFFER_SIZE(cmd_buf), NULL, 0); + else + ret = scm_call2(SCM_SIP_FNID(SCM_SVC_LMH, + LMH_GET_INTENSITY), &desc_arg); + /* Have memory barrier before we access the TZ data */ + mb(); + trace_lmh_event_call("GET_INTENSITY exit"); + if (ret) { + pr_err("Error in SCM v%d read call. err:%d\n", + (is_scm_armv8()) ? 8 : 7, ret); + goto read_exit; + } + + for (idx = 0; idx < payload.count; idx++) { + list_for_each_entry(lmh_sensor, &lmh_sensor_list, list_ptr) { + + if (payload.sensor[idx].name + == lmh_sensor->sensor_hw_name + && (payload.sensor[idx].node_id + == lmh_sensor->sensor_hw_node_id)) { + + lmh_sensor->last_read_value = + (payload.sensor[idx].max_intensity) ? + ((payload.sensor[idx].intensity * 100) + / payload.sensor[idx].max_intensity) + : payload.sensor[idx].intensity; + trace_lmh_sensor_reading( + lmh_sensor->sensor_name, + lmh_sensor->last_read_value); + break; + } + } + } + +read_exit: + mutex_unlock(&lmh_sensor_read); + list_for_each_entry(lmh_sensor, &lmh_sensor_list, list_ptr) + lmh_update(lmh_dat, lmh_sensor); + + return; +} + +static void lmh_poll(struct work_struct *work) +{ + struct lmh_driver_data *lmh_dat = container_of(work, + struct lmh_driver_data, poll_work.work); + + down_write(&lmh_sensor_access); + if (lmh_dat->intr_state != LMH_ISR_POLLING) + goto poll_exit; + lmh_read_and_update(lmh_dat); + if (!lmh_data->intr_status_val) { + lmh_data->intr_state = LMH_ISR_MONITOR; + pr_debug("Zero throttling. Re-enabling interrupt\n"); + trace_lmh_event_call("Lmh Interrupt Clear"); + enable_irq(lmh_data->irq_num); + goto poll_exit; + } else { + queue_delayed_work(lmh_dat->poll_wq, &lmh_dat->poll_work, + msecs_to_jiffies(lmh_poll_interval)); + } + +poll_exit: + up_write(&lmh_sensor_access); + return; +} + +static void lmh_trim_error(void) +{ + struct scm_desc desc_arg; + int ret = 0; + + WARN_ON(1); + pr_err("LMH hardware trim error\n"); + desc_arg.arginfo = SCM_ARGS(0); + trace_lmh_event_call("TRIM_ERROR enter"); + if (!is_scm_armv8()) + ret = scm_call(SCM_SVC_LMH, LMH_TRIM_ERROR, NULL, 0, NULL, 0); + else + ret = scm_call2(SCM_SIP_FNID(SCM_SVC_LMH, + LMH_TRIM_ERROR), &desc_arg); + trace_lmh_event_call("TRIM_ERROR exit"); + if (ret) + pr_err("Error in SCM v%d trim error call. err:%d\n", + (is_scm_armv8()) ? 8 : 7, ret); + + return; +} + +static irqreturn_t lmh_handle_isr(int irq, void *dev_id) +{ + disable_irq_nosync(irq); + return IRQ_WAKE_THREAD; +} + +static irqreturn_t lmh_isr_thread(int irq, void *data) +{ + struct lmh_driver_data *lmh_dat = data; + + pr_debug("LMH Interrupt triggered\n"); + trace_lmh_event_call("Lmh Interrupt"); + + down_write(&lmh_sensor_access); + if (lmh_dat->intr_state != LMH_ISR_MONITOR) { + pr_err("Invalid software state\n"); + trace_lmh_event_call("Invalid software state"); + WARN_ON(1); + goto isr_unlock_exit; + } + lmh_dat->intr_state = LMH_ISR_POLLING; + if (!lmh_data->trim_err_disable) { + lmh_dat->intr_reg_val = readl_relaxed(lmh_dat->intr_addr); + pr_debug("Lmh hw interrupt:%d\n", lmh_dat->intr_reg_val); + if (lmh_dat->intr_reg_val & BIT(lmh_dat->trim_err_offset)) { + trace_lmh_event_call("Lmh trim error"); + lmh_trim_error(); + lmh_dat->intr_state = LMH_ISR_MONITOR; + goto decide_next_action; + } + } + lmh_read_and_update(lmh_dat); + if (!lmh_dat->intr_status_val) { + pr_debug("LMH not throttling. Enabling interrupt\n"); + lmh_dat->intr_state = LMH_ISR_MONITOR; + trace_lmh_event_call("Lmh Zero throttle Interrupt Clear"); + goto decide_next_action; + } + +decide_next_action: + if (lmh_dat->intr_state == LMH_ISR_POLLING) + queue_delayed_work(lmh_dat->poll_wq, &lmh_dat->poll_work, + msecs_to_jiffies(lmh_poll_interval)); + else + enable_irq(lmh_dat->irq_num); + +isr_unlock_exit: + up_write(&lmh_sensor_access); + return IRQ_HANDLED; +} + +static int lmh_get_sensor_devicetree(struct platform_device *pdev) +{ + int ret = 0, idx = 0; + char *key = NULL; + struct device_node *node = pdev->dev.of_node; + struct resource *lmh_intr_base = NULL; + + lmh_data->trim_err_disable = false; + key = "qcom,lmh-trim-err-offset"; + ret = of_property_read_u32(node, key, + &lmh_data->trim_err_offset); + if (ret) { + if (ret == -EINVAL) { + lmh_data->trim_err_disable = true; + ret = 0; + } else { + pr_err("Error reading:%s. err:%d\n", key, ret); + goto dev_exit; + } + } + + lmh_data->regulator = devm_regulator_get(lmh_data->dev, "vdd-apss"); + if (IS_ERR(lmh_data->regulator)) { + pr_err("unable to get vdd-apss regulator. err:%ld\n", + PTR_ERR(lmh_data->regulator)); + lmh_data->regulator = NULL; + } else { + key = "qcom,lmh-odcm-disable-threshold-mA"; + ret = of_property_read_u32(node, key, + &lmh_data->odcm_thresh_mV); + if (ret) { + pr_err("Error getting ODCM thresh. err:%d\n", ret); + ret = 0; + } else { + lmh_data->odcm_enabled = true; + for (; idx < LMH_ODCM_MAX_COUNT; idx++) { + lmh_data->odcm_reg[idx] = + devm_ioremap(&pdev->dev, + lmh_hw_data->odcm_reg_addr[idx], 4); + if (!lmh_data->odcm_reg[idx]) { + pr_err("Err mapping ODCM memory 0x%x\n", + lmh_hw_data->odcm_reg_addr[idx]); + lmh_data->odcm_enabled = false; + lmh_data->odcm_reg[0] = NULL; + break; + } + } + } + } + + lmh_data->irq_num = platform_get_irq(pdev, 0); + if (lmh_data->irq_num < 0) { + ret = lmh_data->irq_num; + pr_err("Error getting IRQ number. err:%d\n", ret); + goto dev_exit; + } + + ret = request_threaded_irq(lmh_data->irq_num, lmh_handle_isr, + lmh_isr_thread, IRQF_TRIGGER_HIGH | IRQF_ONESHOT, + LMH_INTERRUPT, lmh_data); + if (ret) { + pr_err("Error getting irq for LMH. err:%d\n", ret); + goto dev_exit; + } + + if (!lmh_data->trim_err_disable) { + lmh_intr_base = platform_get_resource(pdev, IORESOURCE_MEM, 0); + if (!lmh_intr_base) { + ret = -EINVAL; + pr_err("Error getting reg MEM for LMH.\n"); + goto dev_exit; + } + lmh_data->intr_addr = + devm_ioremap(&pdev->dev, lmh_intr_base->start, + resource_size(lmh_intr_base)); + if (!lmh_data->intr_addr) { + ret = -ENODEV; + pr_err("Error Mapping LMH memory address\n"); + goto dev_exit; + } + } + +dev_exit: + return ret; +} + +static void lmh_remove_sensors(void) +{ + struct lmh_sensor_data *curr_sensor = NULL, *prev_sensor = NULL; + + down_write(&lmh_sensor_access); + list_for_each_entry_safe(prev_sensor, curr_sensor, &lmh_sensor_list, + list_ptr) { + list_del(&prev_sensor->list_ptr); + pr_debug("Deregistering Sensor:[%s]\n", + prev_sensor->sensor_name); + lmh_sensor_deregister(&prev_sensor->ops); + devm_kfree(lmh_data->dev, prev_sensor); + } + up_write(&lmh_sensor_access); +} + +static int lmh_check_tz_debug_cmds(void) +{ + LMH_CHECK_SCM_CMD(LMH_DEBUG_SET); + LMH_CHECK_SCM_CMD(LMH_DEBUG_READ_BUF_SIZE); + LMH_CHECK_SCM_CMD(LMH_DEBUG_READ); + LMH_CHECK_SCM_CMD(LMH_DEBUG_GET_TYPE); + + return 0; +} + +static int lmh_check_tz_dev_cmds(void) +{ + LMH_CHECK_SCM_CMD(LMH_CHANGE_PROFILE); + LMH_CHECK_SCM_CMD(LMH_GET_PROFILES); + + return 0; +} + +static int lmh_check_tz_sensor_cmds(void) +{ + LMH_CHECK_SCM_CMD(LMH_CTRL_QPMDA); + if (!lmh_data->trim_err_disable) + LMH_CHECK_SCM_CMD(LMH_TRIM_ERROR); + LMH_CHECK_SCM_CMD(LMH_GET_INTENSITY); + LMH_CHECK_SCM_CMD(LMH_GET_SENSORS); + + return 0; +} + +static int lmh_parse_sensor(struct lmh_sensor_info *sens_info) +{ + int ret = 0, idx = 0, size = 0; + struct lmh_sensor_data *lmh_sensor = NULL; + + lmh_sensor = devm_kzalloc(lmh_data->dev, sizeof(struct lmh_sensor_data), + GFP_KERNEL); + if (!lmh_sensor) { + pr_err("No payload\n"); + return -ENOMEM; + } + size = sizeof(sens_info->name); + size = min(size, LMH_NAME_MAX); + memset(lmh_sensor->sensor_name, '\0', LMH_NAME_MAX); + while (size--) + lmh_sensor->sensor_name[idx++] = ((sens_info->name + & (0xFF << (size * 8))) >> (size * 8)); + if (lmh_sensor->sensor_name[idx - 1] == '\0') + idx--; + lmh_sensor->sensor_name[idx++] = '_'; + size = sizeof(sens_info->node_id); + if ((idx + size) > LMH_NAME_MAX) + size -= LMH_NAME_MAX - idx - size - 1; + while (size--) + lmh_sensor->sensor_name[idx++] = ((sens_info->node_id + & (0xFF << (size * 8))) >> (size * 8)); + pr_info("Registering sensor:[%s]\n", lmh_sensor->sensor_name); + lmh_sensor->ops.read = lmh_read; + lmh_sensor->ops.disable_hw_log = lmh_disable_log; + lmh_sensor->ops.enable_hw_log = lmh_enable_log; + lmh_sensor->sensor_sw_id = lmh_data->max_sensor_count++; + lmh_sensor->sensor_hw_name = sens_info->name; + lmh_sensor->sensor_hw_node_id = sens_info->node_id; + ret = lmh_sensor_register(lmh_sensor->sensor_name, &lmh_sensor->ops); + if (ret) { + pr_err("Sensor:[%s] registration failed. err:%d\n", + lmh_sensor->sensor_name, ret); + goto sens_exit; + } + list_add_tail(&lmh_sensor->list_ptr, &lmh_sensor_list); + pr_debug("Registered sensor:[%s] driver\n", lmh_sensor->sensor_name); + +sens_exit: + if (ret) + devm_kfree(lmh_data->dev, lmh_sensor); + return ret; +} + +static int lmh_get_sensor_list(void) +{ + int ret = 0; + uint32_t size = 0, next = 0, idx = 0, count = 0; + struct scm_desc desc_arg; + struct lmh_sensor_packet *payload = NULL; + struct { + uint32_t addr; + uint32_t size; + } cmd_buf; + dma_addr_t payload_phys; + DEFINE_DMA_ATTRS(attrs); + struct device dev = {0}; + + dev.coherent_dma_mask = DMA_BIT_MASK(sizeof(dma_addr_t) * 8); + dma_set_attr(DMA_ATTR_WRITE_BARRIER, &attrs); + payload = dma_alloc_attrs(&dev, + PAGE_ALIGN(sizeof(struct lmh_sensor_packet)), + &payload_phys, GFP_KERNEL, &attrs); + if (!payload) { + pr_err("No payload\n"); + return -ENOMEM; + } + + do { + payload->count = next; + cmd_buf.addr = payload_phys; + /* payload_phys may be a physical address > 4 GB */ + desc_arg.args[0] = payload_phys; + desc_arg.args[1] = cmd_buf.size = SCM_BUFFER_SIZE(struct + lmh_sensor_packet); + desc_arg.arginfo = SCM_ARGS(2, SCM_RW, SCM_VAL); + trace_lmh_event_call("GET_SENSORS enter"); + if (!is_scm_armv8()) + ret = scm_call(SCM_SVC_LMH, LMH_GET_SENSORS, + (void *) &cmd_buf, + SCM_BUFFER_SIZE(cmd_buf), + NULL, 0); + else + ret = scm_call2(SCM_SIP_FNID(SCM_SVC_LMH, + LMH_GET_SENSORS), &desc_arg); + /* Have memory barrier before we access the TZ data */ + mb(); + trace_lmh_event_call("GET_SENSORS exit"); + if (ret < 0) { + pr_err("Error in SCM v%d call. err:%d\n", + (is_scm_armv8()) ? 8 : 7, ret); + goto get_exit; + } + size = payload->count; + if (!size) { + pr_err("No LMH sensor supported\n"); + ret = -ENODEV; + goto get_exit; + } + count = ((size - next) > LMH_MAX_SENSOR) ? LMH_MAX_SENSOR : + (size - next); + next += LMH_MAX_SENSOR; + for (idx = 0; idx < count; idx++) { + ret = lmh_parse_sensor(&payload->sensor[idx]); + if (ret) + goto get_exit; + } + } while (next < size); + +get_exit: + dma_free_attrs(&dev, size, payload, payload_phys, &attrs); + return ret; +} + +static int lmh_set_level(struct lmh_device_ops *ops, int level) +{ + int ret = 0, idx = 0; + struct scm_desc desc_arg; + struct lmh_profile *lmh_dev; + + if (level < 0 || !ops) { + pr_err("Invalid Input\n"); + return -EINVAL; + } + lmh_dev = container_of(ops, struct lmh_profile, dev_ops); + for (idx = 0; idx < lmh_dev->level_ct; idx++) { + if (level != lmh_dev->levels[idx]) + continue; + break; + } + if (idx == lmh_dev->level_ct) { + pr_err("Invalid profile:[%d]\n", level); + return -EINVAL; + } + desc_arg.args[0] = level; + desc_arg.arginfo = SCM_ARGS(1, SCM_VAL); + if (!is_scm_armv8()) + ret = scm_call_atomic1(SCM_SVC_LMH, LMH_CHANGE_PROFILE, + level); + else + ret = scm_call2(SCM_SIP_FNID(SCM_SVC_LMH, + LMH_CHANGE_PROFILE), &desc_arg); + if (ret) { + pr_err("Error in SCM v%d switching profile:[%d]. err:%d\n", + (is_scm_armv8()) ? 8 : 7, level, ret); + return ret; + } + pr_debug("Device:[%s] Current level:%d\n", LMH_DEVICE, level); + lmh_dev->curr_level = level; + + return ret; + +} + +static int lmh_get_all_level(struct lmh_device_ops *ops, int *level) +{ + struct lmh_profile *lmh_dev; + + if (!ops) { + pr_err("Invalid input\n"); + return -EINVAL; + } + lmh_dev = container_of(ops, struct lmh_profile, dev_ops); + if (!level) + return lmh_dev->level_ct; + memcpy(level, lmh_dev->levels, lmh_dev->level_ct * sizeof(uint32_t)); + + return 0; +} + + +static int lmh_get_level(struct lmh_device_ops *ops, int *level) +{ + struct lmh_profile *lmh_dev; + + if (!level || !ops) { + pr_err("Invalid input\n"); + return -EINVAL; + } + lmh_dev = container_of(ops, struct lmh_profile, dev_ops); + + *level = lmh_dev->curr_level; + + return 0; +} + +static int lmh_get_dev_info(void) +{ + int ret = 0; + uint32_t size = 0, next = 0; + struct scm_desc desc_arg; + uint32_t *payload = NULL; + struct { + uint32_t list_addr; + uint32_t list_size; + uint32_t list_start; + } cmd_buf; + + payload = devm_kzalloc(lmh_data->dev, sizeof(uint32_t) * + LMH_GET_PROFILE_SIZE, GFP_KERNEL); + if (!payload) { + pr_err("No payload\n"); + ret = -ENOMEM; + goto get_dev_exit; + } + + cmd_buf.list_addr = SCM_BUFFER_PHYS(payload); + /* &payload may be a physical address > 4 GB */ + desc_arg.args[0] = SCM_BUFFER_PHYS(payload); + desc_arg.args[1] = cmd_buf.list_size = + SCM_BUFFER_SIZE(uint32_t) * LMH_GET_PROFILE_SIZE; + desc_arg.arginfo = SCM_ARGS(3, SCM_RW, SCM_VAL, SCM_VAL); + LMH_GET_RECURSSIVE_DATA(desc_arg, 2, cmd_buf, payload, next, size, + LMH_GET_PROFILES, lmh_data->dev_info.levels, ret); + if (ret) + goto get_dev_exit; + lmh_data->dev_info.level_ct = size; + lmh_data->dev_info.curr_level = LMH_DEFAULT_PROFILE; + ret = lmh_set_level(&lmh_data->dev_info.dev_ops, + lmh_hw_data->default_profile); + if (ret) { + pr_err("Error switching to default profile%d, err:%d\n", + lmh_data->dev_info.curr_level, ret); + goto get_dev_exit; + } + +get_dev_exit: + if (ret) + devm_kfree(lmh_data->dev, lmh_data->dev_info.levels); + devm_kfree(lmh_data->dev, payload); + return ret; +} + +static int lmh_device_init(void) +{ + int ret = 0; + + if (lmh_check_tz_dev_cmds()) + return -ENODEV; + + ret = lmh_get_dev_info(); + if (ret) + goto dev_init_exit; + + lmh_data->dev_info.dev_ops.get_available_levels = lmh_get_all_level; + lmh_data->dev_info.dev_ops.get_curr_level = lmh_get_level; + lmh_data->dev_info.dev_ops.set_level = lmh_set_level; + ret = lmh_device_register(LMH_DEVICE, &lmh_data->dev_info.dev_ops); + if (ret) { + pr_err("Error registering device:[%s]. err:%d", LMH_DEVICE, + ret); + goto dev_init_exit; + } + +dev_init_exit: + return ret; +} + +static int lmh_debug_read(struct lmh_debug_ops *ops, uint32_t **buf) +{ + int ret = 0, size = 0, tz_ret = 0; + static uint32_t curr_size; + struct scm_desc desc_arg; + static uint32_t *payload; + struct { + uint32_t buf_addr; + uint32_t buf_size; + } cmd_buf; + + desc_arg.arginfo = SCM_ARGS(0); + trace_lmh_event_call("GET_DEBUG_READ_SIZE enter"); + if (!is_scm_armv8()) { + ret = scm_call(SCM_SVC_LMH, LMH_DEBUG_READ_BUF_SIZE, + NULL, 0, &size, SCM_BUFFER_SIZE(size)); + } else { + ret = scm_call2(SCM_SIP_FNID(SCM_SVC_LMH, + LMH_DEBUG_READ_BUF_SIZE), &desc_arg); + size = desc_arg.ret[0]; + } + trace_lmh_event_call("GET_DEBUG_READ_SIZE exit"); + if (ret) { + pr_err("Error in SCM v%d get debug buffer size call. err:%d\n", + (is_scm_armv8()) ? 8 : 7, ret); + goto get_dbg_exit; + } + if (!size) { + pr_err("No Debug data to read.\n"); + ret = -ENODEV; + goto get_dbg_exit; + } + size = SCM_BUFFER_SIZE(uint32_t) * size * LMH_READ_LINE_LENGTH; + if (curr_size != size) { + if (payload) + devm_kfree(lmh_data->dev, payload); + payload = devm_kzalloc(lmh_data->dev, size, GFP_KERNEL); + if (!payload) { + pr_err("payload buffer alloc failed\n"); + ret = -ENOMEM; + goto get_dbg_exit; + } + curr_size = size; + } + + cmd_buf.buf_addr = SCM_BUFFER_PHYS(payload); + /* &payload may be a physical address > 4 GB */ + desc_arg.args[0] = SCM_BUFFER_PHYS(payload); + desc_arg.args[1] = cmd_buf.buf_size = curr_size; + desc_arg.arginfo = SCM_ARGS(2, SCM_RW, SCM_VAL); + trace_lmh_event_call("GET_DEBUG_READ enter"); + dmac_flush_range(payload, payload + curr_size); + if (!is_scm_armv8()) { + ret = scm_call(SCM_SVC_LMH, LMH_DEBUG_READ, + (void *) &cmd_buf, SCM_BUFFER_SIZE(cmd_buf), + &tz_ret, SCM_BUFFER_SIZE(tz_ret)); + } else { + ret = scm_call2(SCM_SIP_FNID(SCM_SVC_LMH, + LMH_DEBUG_READ), &desc_arg); + tz_ret = desc_arg.ret[0]; + } + /* Have memory barrier before we access the TZ data */ + mb(); + trace_lmh_event_call("GET_DEBUG_READ exit"); + if (ret) { + pr_err("Error in SCM v%d get debug read. err:%d\n", + (is_scm_armv8()) ? 8 : 7, ret); + goto get_dbg_exit; + } + if (tz_ret) { + pr_err("TZ API returned error. err:%d\n", tz_ret); + ret = tz_ret; + goto get_dbg_exit; + } + trace_lmh_debug_data("Debug read", payload, + curr_size / sizeof(uint32_t)); + +get_dbg_exit: + if (ret && payload) { + devm_kfree(lmh_data->dev, payload); + payload = NULL; + curr_size = 0; + } + *buf = payload; + + return (ret < 0) ? ret : curr_size; +} + +static int lmh_debug_config_write(uint32_t cmd_id, uint32_t *buf, int size) +{ + int ret = 0, size_bytes = 0; + struct scm_desc desc_arg; + uint32_t *payload = NULL; + struct { + uint32_t buf_addr; + uint32_t buf_size; + uint32_t node; + uint32_t node_id; + uint32_t read_type; + } cmd_buf; + + trace_lmh_debug_data("Config LMH", buf, size); + size_bytes = (size - 3) * sizeof(uint32_t); + payload = devm_kzalloc(lmh_data->dev, size_bytes, GFP_KERNEL); + if (!payload) { + ret = -ENOMEM; + goto set_cfg_exit; + } + memcpy(payload, &buf[3], size_bytes); + + cmd_buf.buf_addr = SCM_BUFFER_PHYS(payload); + /* &payload may be a physical address > 4 GB */ + desc_arg.args[0] = SCM_BUFFER_PHYS(payload); + desc_arg.args[1] = cmd_buf.buf_size = size_bytes; + desc_arg.args[2] = cmd_buf.node = buf[0]; + desc_arg.args[3] = cmd_buf.node_id = buf[1]; + desc_arg.args[4] = cmd_buf.read_type = buf[2]; + desc_arg.arginfo = SCM_ARGS(5, SCM_RO, SCM_VAL, SCM_VAL, SCM_VAL, + SCM_VAL); + trace_lmh_event_call("CONFIG_DEBUG_WRITE enter"); + dmac_flush_range(payload, payload + size_bytes); + if (!is_scm_armv8()) + ret = scm_call(SCM_SVC_LMH, cmd_id, (void *) &cmd_buf, + SCM_BUFFER_SIZE(cmd_buf), NULL, 0); + else + ret = scm_call2(SCM_SIP_FNID(SCM_SVC_LMH, cmd_id), &desc_arg); + /* Have memory barrier before we access the TZ data */ + mb(); + trace_lmh_event_call("CONFIG_DEBUG_WRITE exit"); + if (ret) { + pr_err("Error in SCM v%d config debug read. err:%d\n", + (is_scm_armv8()) ? 8 : 7, ret); + goto set_cfg_exit; + } + +set_cfg_exit: + return ret; +} + +static int lmh_debug_config_read(struct lmh_debug_ops *ops, uint32_t *buf, + int size) +{ + return lmh_debug_config_write(LMH_DEBUG_SET, buf, size); +} + +static int lmh_debug_get_types(struct lmh_debug_ops *ops, bool is_read, + uint32_t **buf) +{ + int ret = 0; + uint32_t size = 0, next = 0; + struct scm_desc desc_arg; + uint32_t *payload = NULL, *dest_buf = NULL; + struct { + uint32_t list_addr; + uint32_t list_size; + uint32_t cmd_type; + uint32_t list_start; + } cmd_buf; + + if (is_read && lmh_data->debug_info.read_type) { + *buf = lmh_data->debug_info.read_type; + trace_lmh_debug_data("Data type", + lmh_data->debug_info.read_type, + lmh_data->debug_info.read_type_count); + return lmh_data->debug_info.read_type_count; + } else if (!is_read && lmh_data->debug_info.config_type) { + *buf = lmh_data->debug_info.config_type; + trace_lmh_debug_data("Config type", + lmh_data->debug_info.config_type, + lmh_data->debug_info.config_type_count); + return lmh_data->debug_info.config_type_count; + } + payload = devm_kzalloc(lmh_data->dev, sizeof(uint32_t) * + LMH_SCM_PAYLOAD_SIZE, GFP_KERNEL); + if (!payload) { + ret = -ENOMEM; + goto get_type_exit; + } + cmd_buf.list_addr = SCM_BUFFER_PHYS(payload); + /* &payload may be a physical address > 4 GB */ + desc_arg.args[0] = SCM_BUFFER_PHYS(payload); + desc_arg.args[1] = cmd_buf.list_size = + SCM_BUFFER_SIZE(uint32_t) * LMH_SCM_PAYLOAD_SIZE; + desc_arg.args[2] = cmd_buf.cmd_type = (is_read) ? + LMH_DEBUG_READ_TYPE : LMH_DEBUG_CONFIG_TYPE; + desc_arg.arginfo = SCM_ARGS(4, SCM_RW, SCM_VAL, SCM_VAL, SCM_VAL); + LMH_GET_RECURSSIVE_DATA(desc_arg, 3, cmd_buf, payload, next, size, + LMH_DEBUG_GET_TYPE, dest_buf, ret); + if (ret) + goto get_type_exit; + pr_debug("Total %s types:%d\n", (is_read) ? "read" : "config", size); + if (is_read) { + lmh_data->debug_info.read_type = *buf = dest_buf; + lmh_data->debug_info.read_type_count = size; + trace_lmh_debug_data("Data type", dest_buf, size); + } else { + lmh_data->debug_info.config_type = *buf = dest_buf; + lmh_data->debug_info.config_type_count = size; + trace_lmh_debug_data("Config type", dest_buf, size); + } + +get_type_exit: + if (ret) { + devm_kfree(lmh_data->dev, lmh_data->debug_info.read_type); + devm_kfree(lmh_data->dev, lmh_data->debug_info.config_type); + lmh_data->debug_info.config_type_count = 0; + lmh_data->debug_info.read_type_count = 0; + } + devm_kfree(lmh_data->dev, payload); + return (ret) ? ret : size; +} + +static int lmh_debug_lmh_config(struct lmh_debug_ops *ops, uint32_t *buf, + int size) +{ + return lmh_debug_config_write(LMH_DEBUG_SET, buf, size); +} + +static void lmh_voltage_scale_set(uint32_t voltage) +{ + char trace_buf[MAX_TRACE_EVENT_MSG_LEN] = ""; + + mutex_lock(&scm_lmh_lock); + writel_relaxed(voltage, lmh_data->dpm_voltage_scale_reg); + mutex_unlock(&scm_lmh_lock); + snprintf(trace_buf, MAX_TRACE_EVENT_MSG_LEN, + "DPM voltage scale %d mV", voltage); + pr_debug("%s\n", trace_buf); + trace_lmh_event_call(trace_buf); +} + +static void write_to_odcm(bool enable) +{ + uint32_t idx = 0, data = enable ? 1 : 0; + + for (; idx < LMH_ODCM_MAX_COUNT; idx++) + writel_relaxed(data, lmh_data->odcm_reg[idx]); +} + +static void evaluate_and_config_odcm(uint32_t rail_uV, unsigned long state) +{ + uint32_t rail_mV = rail_uV / 1000; + static bool prev_state, disable_odcm; + + mutex_lock(&lmh_odcm_access); + switch (state) { + case REGULATOR_EVENT_VOLTAGE_CHANGE: + if (!disable_odcm) + break; + pr_debug("Disable ODCM\n"); + write_to_odcm(false); + lmh_data->odcm_enabled = false; + disable_odcm = false; + break; + case REGULATOR_EVENT_PRE_VOLTAGE_CHANGE: + disable_odcm = false; + prev_state = lmh_data->odcm_enabled; + if (rail_mV > lmh_data->odcm_thresh_mV) { + if (lmh_data->odcm_enabled) + break; + /* Enable ODCM before the voltage increases */ + pr_debug("Enable ODCM for voltage %u mV\n", rail_mV); + write_to_odcm(true); + lmh_data->odcm_enabled = true; + } else { + if (!lmh_data->odcm_enabled) + break; + /* Disable ODCM after the voltage decreases */ + pr_debug("Disable ODCM for voltage %u mV\n", rail_mV); + disable_odcm = true; + } + break; + case REGULATOR_EVENT_ABORT_VOLTAGE_CHANGE: + disable_odcm = false; + if (prev_state == lmh_data->odcm_enabled) + break; + pr_debug("Reverting ODCM state to %s\n", + prev_state ? "enabled" : "disabled"); + write_to_odcm(prev_state); + lmh_data->odcm_enabled = prev_state; + break; + default: + break; + } + mutex_unlock(&lmh_odcm_access); +} + +static int lmh_voltage_change_notifier(struct notifier_block *nb_data, + unsigned long event, void *data) +{ + uint32_t voltage = 0; + static uint32_t last_voltage; + static bool change_needed; + + if (event == REGULATOR_EVENT_VOLTAGE_CHANGE) { + /* Convert from uV to mV */ + pr_debug("Received event POST_VOLTAGE_CHANGE\n"); + voltage = ((unsigned long)data) / 1000; + if (change_needed == 1 && + (last_voltage == voltage)) { + lmh_voltage_scale_set(voltage); + change_needed = 0; + } + if (lmh_data->odcm_reg[0]) + evaluate_and_config_odcm(0, event); + } else if (event == REGULATOR_EVENT_PRE_VOLTAGE_CHANGE) { + struct pre_voltage_change_data *change_data = + (struct pre_voltage_change_data *)data; + last_voltage = change_data->min_uV / 1000; + if (change_data->min_uV > change_data->old_uV) + /* Going from low to high apply change first */ + lmh_voltage_scale_set(last_voltage); + else + /* Going from high to low apply change after */ + change_needed = 1; + pr_debug("Received event PRE_VOLTAGE_CHANGE\n"); + pr_debug("max = %lu mV min = %lu mV previous = %lu mV\n", + change_data->max_uV / 1000, change_data->min_uV / 1000, + change_data->old_uV / 1000); + + if (lmh_data->odcm_reg[0]) + evaluate_and_config_odcm(change_data->max_uV, event); + } else if (event == REGULATOR_EVENT_ABORT_VOLTAGE_CHANGE) { + pr_debug("Received event ABORT_VOLTAGE_CHANGE\n"); + if (lmh_data->odcm_reg[0]) + evaluate_and_config_odcm(0, event); + } + + return NOTIFY_OK; +} + +static void lmh_dpm_remove(void) +{ + if (!IS_ERR_OR_NULL(lmh_data->regulator) && + lmh_data->dpm_notifier_blk.notifier_call != NULL) { + regulator_unregister_notifier(lmh_data->regulator, + &(lmh_data->dpm_notifier_blk)); + lmh_data->regulator = NULL; + } +} + +static void lmh_dpm_init(void) +{ + int ret = 0; + + lmh_data->dpm_voltage_scale_reg = devm_ioremap(lmh_data->dev, + (phys_addr_t)APCS_DPM_VOLTAGE_SCALE, 4); + if (!lmh_data->dpm_voltage_scale_reg) { + ret = -ENODEV; + pr_err("Error mapping LMH DPM voltage scale register\n"); + goto dpm_init_exit; + } + + lmh_data->dpm_notifier_blk.notifier_call = lmh_voltage_change_notifier; + ret = regulator_register_notifier(lmh_data->regulator, + &(lmh_data->dpm_notifier_blk)); + if (ret) { + pr_err("DPM regulator notification registration failed. err:%d\n", + ret); + goto dpm_init_exit; + } + +dpm_init_exit: + if (ret) { + if (lmh_data->dpm_notifier_blk.notifier_call) + regulator_unregister_notifier(lmh_data->regulator, + &(lmh_data->dpm_notifier_blk)); + devm_regulator_put(lmh_data->regulator); + lmh_data->dpm_notifier_blk.notifier_call = NULL; + lmh_data->regulator = NULL; + } +} + + +static int lmh_debug_init(void) +{ + int ret = 0; + + if (lmh_check_tz_debug_cmds()) { + pr_debug("Debug commands not available.\n"); + return -ENODEV; + } + + lmh_data->debug_info.debug_ops.debug_read = lmh_debug_read; + lmh_data->debug_info.debug_ops.debug_config_read + = lmh_debug_config_read; + lmh_data->debug_info.debug_ops.debug_config_lmh + = lmh_debug_lmh_config; + lmh_data->debug_info.debug_ops.debug_get_types + = lmh_debug_get_types; + ret = lmh_debug_register(&lmh_data->debug_info.debug_ops); + if (ret) { + pr_err("Error registering debug ops. err:%d\n", ret); + goto debug_init_exit; + } + +debug_init_exit: + return ret; +} +static int lmh_sensor_init(struct platform_device *pdev) +{ + int ret = 0; + + if (lmh_check_tz_sensor_cmds()) + return -ENODEV; + + down_write(&lmh_sensor_access); + ret = lmh_get_sensor_list(); + if (ret) + goto init_exit; + + lmh_data->intr_state = LMH_ISR_MONITOR; + + ret = lmh_get_sensor_devicetree(pdev); + if (ret) { + pr_err("Error getting device tree data. err:%d\n", ret); + goto init_exit; + } + pr_debug("LMH Sensor Init complete\n"); + +init_exit: + up_write(&lmh_sensor_access); + if (ret) + lmh_remove_sensors(); + + return ret; +} + +static int lmh_probe(struct platform_device *pdev) +{ + int ret = 0; + + if (lmh_data) { + pr_err("Reinitializing lmh hardware driver\n"); + return -EEXIST; + } + lmh_data = devm_kzalloc(&pdev->dev, sizeof(struct lmh_driver_data), + GFP_KERNEL); + if (!lmh_data) { + pr_err("kzalloc failed\n"); + return -ENOMEM; + } + lmh_data->dev = &pdev->dev; + + lmh_data->poll_wq = alloc_workqueue("lmh_poll_wq", WQ_HIGHPRI, 0); + if (!lmh_data->poll_wq) { + pr_err("Error allocating workqueue\n"); + ret = -ENOMEM; + goto probe_exit; + } + INIT_DEFERRABLE_WORK(&lmh_data->poll_work, lmh_poll); + + ret = lmh_sensor_init(pdev); + if (ret) { + pr_err("Sensor Init failed. err:%d\n", ret); + goto probe_exit; + } + ret = lmh_device_init(); + if (ret) { + pr_err("WARNING: Device Init failed. err:%d. LMH continues\n", + ret); + ret = 0; + } + + if (lmh_data->regulator) + lmh_dpm_init(); + + ret = lmh_debug_init(); + if (ret) { + pr_err("LMH debug init failed. err:%d\n", ret); + ret = 0; + } + platform_set_drvdata(pdev, lmh_data); + + return ret; + +probe_exit: + if (lmh_data->poll_wq) + destroy_workqueue(lmh_data->poll_wq); + lmh_data = NULL; + return ret; +} + +static int lmh_remove(struct platform_device *pdev) +{ + struct lmh_driver_data *lmh_dat = platform_get_drvdata(pdev); + + destroy_workqueue(lmh_dat->poll_wq); + free_irq(lmh_dat->irq_num, lmh_dat); + lmh_remove_sensors(); + lmh_device_deregister(&lmh_dat->dev_info.dev_ops); + lmh_dpm_remove(); + + return 0; +} + +static struct of_device_id lmh_match[] = { + { + .compatible = "qcom,lmh", + .data = (void *)&lmh_lite_data, + }, + { + .compatible = "qcom,lmh_v1", + .data = (void *)&lmh_v1_data, + }, + {}, +}; + +static struct platform_driver lmh_driver = { + .probe = lmh_probe, + .remove = lmh_remove, + .driver = { + .name = LMH_DRIVER_NAME, + .owner = THIS_MODULE, + .of_match_table = lmh_match, + }, +}; + +int __init lmh_init_driver(void) +{ + struct device_node *comp_node; + + comp_node = of_find_matching_node(NULL, lmh_match); + if (comp_node) { + const struct of_device_id *match = of_match_node(lmh_match, + comp_node); + if (!match) { + pr_err("Couldnt find a match\n"); + goto plt_register; + } + lmh_hw_data = (struct lmh_default_data *)match->data; + of_node_put(comp_node); + } + +plt_register: + return platform_driver_register(&lmh_driver); +} + +static void __exit lmh_exit(void) +{ + platform_driver_unregister(&lmh_driver); +} + +late_initcall(lmh_init_driver); +module_exit(lmh_exit); + +MODULE_DESCRIPTION("LMH hardware interface"); +MODULE_ALIAS("platform:" LMH_DRIVER_NAME); +MODULE_LICENSE("GPL v2"); |