diff options
| -rw-r--r-- | Documentation/devicetree/bindings/mmc/sdhci-msm.txt | 12 | ||||
| -rw-r--r-- | arch/arm/boot/dts/qcom/msm8996pro-auto-adp.dts | 9 | ||||
| -rw-r--r-- | drivers/i2c/busses/Kconfig | 6 | ||||
| -rw-r--r-- | drivers/i2c/busses/Makefile | 1 | ||||
| -rw-r--r-- | drivers/i2c/busses/virtio-i2c.c | 356 | ||||
| -rw-r--r-- | drivers/media/platform/msm/camera_v2/isp/msm_isp.c | 6 | ||||
| -rw-r--r-- | drivers/media/platform/msm/camera_v2/isp/msm_isp.h | 1 | ||||
| -rw-r--r-- | drivers/media/platform/msm/camera_v2/isp/msm_isp47.c | 9 | ||||
| -rw-r--r-- | drivers/mmc/core/core.c | 10 | ||||
| -rw-r--r-- | drivers/mmc/core/sd.c | 40 | ||||
| -rw-r--r-- | drivers/mmc/host/sdhci-msm.c | 178 | ||||
| -rw-r--r-- | drivers/mmc/host/sdhci-msm.h | 12 | ||||
| -rw-r--r-- | drivers/mmc/host/sdhci.c | 24 | ||||
| -rw-r--r-- | drivers/mmc/host/sdhci.h | 3 | ||||
| -rw-r--r-- | include/linux/mmc/host.h | 3 | ||||
| -rw-r--r-- | include/uapi/linux/virtio_ids.h | 3 |
16 files changed, 668 insertions, 5 deletions
diff --git a/Documentation/devicetree/bindings/mmc/sdhci-msm.txt b/Documentation/devicetree/bindings/mmc/sdhci-msm.txt index 9916c34e62b8..380e8453b4ce 100644 --- a/Documentation/devicetree/bindings/mmc/sdhci-msm.txt +++ b/Documentation/devicetree/bindings/mmc/sdhci-msm.txt @@ -113,6 +113,18 @@ In the following, <supply> can be vdd (flash core voltage) or vdd-io (I/O voltag - qcom,wakeup-on-idle: if configured, the mmcqd thread will call set_wake_up_idle(), thereby voting for it to be called on idle CPUs. + - qcom,late-sdhci-msm: if configured, the sdhci probe will be called in + late_init() call context and probe will be delayed. + + - qcom,tsens-id: temperature sensor id which is closest to SDC host controller. + + - qcom,disable_scaling_threshold_temp: temperature value at which scaling is + disabled if the temperature falls below this temperature. + + - qcom,enable_scaling_threshold_temp: temperature value at which scaling is + enabled, when the scaling is disabled, if temperature rises above this + temperature. + Example: aliases { diff --git a/arch/arm/boot/dts/qcom/msm8996pro-auto-adp.dts b/arch/arm/boot/dts/qcom/msm8996pro-auto-adp.dts index e17556ac43be..f4b209cf69cc 100644 --- a/arch/arm/boot/dts/qcom/msm8996pro-auto-adp.dts +++ b/arch/arm/boot/dts/qcom/msm8996pro-auto-adp.dts @@ -73,11 +73,20 @@ }; }; +&sdhc_1 { + qcom,tsens-id = <0>; +}; + &sdhc_2 { + compatible = "qcom,late-sdhci-msm"; + cd-gpios = <&tlmm 38 GPIO_ACTIVE_LOW>; pinctrl-0 = <&sdc2_clk_on &sdc2_cmd_on &sdc2_data_on &sdc2_cd_on_sbc>; pinctrl-1 = <&sdc2_clk_off &sdc2_cmd_off &sdc2_data_off &sdc2_cd_on_sbc>; + qcom,tsens-id = <10>; + qcom,disable_scaling_threshold_temp = <(-15)>; + qcom,enable_scaling_threshold_temp = <(-5)>; }; &i2c_7 { diff --git a/drivers/i2c/busses/Kconfig b/drivers/i2c/busses/Kconfig index 5b57c7edbc72..192c850a8b92 100644 --- a/drivers/i2c/busses/Kconfig +++ b/drivers/i2c/busses/Kconfig @@ -1209,5 +1209,11 @@ config I2C_MSM_V2 This driver can also be built as a module. If so, the module will be called i2c-msm-v2. +config VIRTIO_I2C + tristate "VIRTIO_I2C" + depends on VIRTIO + help + If you say yes to this option, the i2c virtualization will be + supported. endmenu diff --git a/drivers/i2c/busses/Makefile b/drivers/i2c/busses/Makefile index 0c9891812aa8..47e5ed02ea7f 100644 --- a/drivers/i2c/busses/Makefile +++ b/drivers/i2c/busses/Makefile @@ -97,6 +97,7 @@ obj-$(CONFIG_I2C_XLR) += i2c-xlr.o obj-$(CONFIG_I2C_XLP9XX) += i2c-xlp9xx.o obj-$(CONFIG_I2C_RCAR) += i2c-rcar.o obj-$(CONFIG_I2C_MSM_V2) += i2c-msm-v2.o +obj-$(CONFIG_VIRTIO_I2C) += virtio-i2c.o # External I2C/SMBus adapter drivers obj-$(CONFIG_I2C_DIOLAN_U2C) += i2c-diolan-u2c.o diff --git a/drivers/i2c/busses/virtio-i2c.c b/drivers/i2c/busses/virtio-i2c.c new file mode 100644 index 000000000000..84d045266143 --- /dev/null +++ b/drivers/i2c/busses/virtio-i2c.c @@ -0,0 +1,356 @@ +/* Copyright (c) 2019, 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 <linux/kernel.h> +#include <linux/module.h> +#include <linux/kthread.h> +#include <linux/scatterlist.h> +#include <linux/dma-mapping.h> +#include <linux/interrupt.h> +#include <linux/slab.h> +#include <linux/idr.h> +#include <linux/jiffies.h> +#include <linux/sched.h> +#include <linux/wait.h> +#include <linux/spinlock.h> +#include <linux/virtio.h> +#include <uapi/linux/virtio_ids.h> +#include <linux/virtio_config.h> +#include <linux/i2c.h> + +#define I2C_ADAPTER_NR 0x00 + +#define I2C_VIRTIO_RD 0x01 +#define I2C_VIRTIO_WR 0x02 +#define I2C_VIRTIO_RDWR 0x03 + +/** + * struct virtio_i2c - virtio i2c device + * @adapter: i2c adapter + * @vdev: the virtio device + * @vq: i2c virtqueue + */ +struct virtio_i2c { + struct i2c_adapter adapter; + struct virtio_device *vdev; + struct virtqueue *vq; + wait_queue_head_t inq; +}; + +struct i2c_transfer_head { + u32 type; /* read or write from or to slave */ + u32 addr; /* slave addr */ + u32 length; /* buffer length */ + u32 total_length; /* merge write and read will use this segment */ +}; + +struct i2c_transfer_end { + u32 result; /* return value from backend */ +}; + +struct virtio_i2c_req { + struct i2c_transfer_head head; + char *buf; + struct i2c_transfer_end end; +}; + +static int virti2c_transfer(struct virtio_i2c *vi2c, + struct virtio_i2c_req *i2c_req) +{ + struct virtqueue *vq = vi2c->vq; + struct scatterlist outhdr, bufhdr, inhdr, *sgs[3]; + unsigned int num_out = 0, num_in = 0, err, len; + struct virtio_i2c_req *req_handled = NULL; + + /* send the head queue to the backend */ + sg_init_one(&outhdr, &i2c_req->head, sizeof(i2c_req->head)); + sgs[num_out++] = &outhdr; + + /* send the buffer queue to the backend */ + sg_init_one(&bufhdr, i2c_req->buf, + (i2c_req->head.type == I2C_VIRTIO_RDWR) ? + i2c_req->head.total_length : i2c_req->head.length); + if (i2c_req->head.type & I2C_VIRTIO_WR) + sgs[num_out++] = &bufhdr; + else + sgs[num_out + num_in++] = &bufhdr; + + /* send the result queue to the backend */ + sg_init_one(&inhdr, &i2c_req->end, sizeof(i2c_req->end)); + sgs[num_out + num_in++] = &inhdr; + + /* call the virtqueue function */ + err = virtqueue_add_sgs(vq, sgs, num_out, num_in, i2c_req, GFP_KERNEL); + if (err) + goto req_exit; + + /* Tell Host to go! */ + err = virtqueue_kick(vq); + + wait_event(vi2c->inq, + (req_handled = virtqueue_get_buf(vq, &len))); + + if (i2c_req->head.type == I2C_VIRTIO_RDWR) { + if (i2c_req->end.result == + i2c_req->head.total_length - i2c_req->head.length) + err = 0; + else + err = -EINVAL; + } else { + if (i2c_req->end.result == i2c_req->head.length) + err = 0; + else + err = -EINVAL; + } +req_exit: + return err; +} + +/* prepare the transfer req */ +static struct virtio_i2c_req *virti2c_transfer_prepare(struct i2c_msg *msg_1, + struct i2c_msg *msg_2) +{ + char *ptr = NULL; + int merge = 0; + struct virtio_i2c_req *i2c_req; + + if (msg_1 == NULL) + return NULL; + + if (msg_2) + merge = 1; + + i2c_req = kzalloc(sizeof(struct virtio_i2c_req), GFP_KERNEL); + if (i2c_req == NULL) + return NULL; + + if (merge) + ptr = kzalloc((msg_1->len + msg_2->len), GFP_KERNEL); + else + ptr = msg_1->buf; + if (ptr == NULL) + goto err_mem; + + /* prepare the head */ + i2c_req->head.type = merge ? + I2C_VIRTIO_RDWR : ((msg_1->flags & I2C_M_RD) ? + I2C_VIRTIO_RD : I2C_VIRTIO_WR); + i2c_req->head.addr = msg_1->addr; + i2c_req->head.length = msg_1->len; + if (merge) + i2c_req->head.total_length = msg_1->len + msg_2->len; + + /* prepare the buf */ + if (merge) + memcpy(ptr, msg_1->buf, msg_1->len); + i2c_req->buf = ptr; + + return i2c_req; +err_mem: + kfree(i2c_req); + i2c_req = NULL; + return NULL; +} + +static void virti2c_transfer_end(struct virtio_i2c_req *req, + struct i2c_msg *msg) +{ + if (req->head.type == I2C_VIRTIO_RDWR) { + memcpy(msg->buf, req->buf + req->head.length, msg->len); + kfree(req->buf); + req->buf = NULL; + } + + kfree(req); + req = NULL; +} + +static int virtio_i2c_master_xfer(struct i2c_adapter *adap, + struct i2c_msg *msgs, int num) +{ + int i, ret; + struct virtio_i2c_req *i2c_req; + struct virtio_i2c *vi2c = i2c_get_adapdata(adap); + + if (num < 1) { + dev_err(&vi2c->vdev->dev, + "error on number of msgs(%d) received\n", num); + return -EINVAL; + } + + if (IS_ERR_OR_NULL(msgs)) { + dev_err(&vi2c->vdev->dev, " error no msgs Accessing invalid pointer location\n"); + return PTR_ERR(msgs); + } + + for (i = 0; i < num; i++) { + + if (msgs[i].flags & I2C_M_RD) { + /* read the data from slave to master*/ + i2c_req = virti2c_transfer_prepare(&msgs[i], NULL); + + } else if ((i + 1 < num) && (msgs[i + 1].flags & I2C_M_RD) && + (msgs[i].addr == msgs[i + 1].addr)) { + /* write then read from same address*/ + i2c_req = virti2c_transfer_prepare(&msgs[i], + &msgs[i+1]); + i += 1; + + } else { + /* write the data to slave */ + i2c_req = virti2c_transfer_prepare(&msgs[i], NULL); + } + + if (i2c_req == NULL) { + ret = -ENOMEM; + goto err; + } + ret = virti2c_transfer(vi2c, i2c_req); + virti2c_transfer_end(i2c_req, &msgs[i]); + if (ret) + goto err; + } + return 0; +err: + return ret; +} + +static u32 virtio_i2c_functionality(struct i2c_adapter *adapter) +{ + return I2C_FUNC_I2C | I2C_FUNC_SMBUS_EMUL; +} + +static struct i2c_algorithm virtio_i2c_algorithm = { + .master_xfer = virtio_i2c_master_xfer, + .functionality = virtio_i2c_functionality, +}; + +/* virtqueue incoming data interrupt IRQ */ +static void virti2c_vq_isr(struct virtqueue *vq) +{ + struct virtio_i2c *vi2c = vq->vdev->priv; + + wake_up(&vi2c->inq); +} + +static int virti2c_init_vqs(struct virtio_i2c *vi2c) +{ + struct virtqueue *vqs[1]; + vq_callback_t *cbs[] = { virti2c_vq_isr }; + static const char * const names[] = { "virti2c_vq_isr" }; + int err; + + err = vi2c->vdev->config->find_vqs(vi2c->vdev, 1, vqs, cbs, names); + if (err) + return err; + vi2c->vq = vqs[0]; + + return 0; +} + +static void virti2c_del_vqs(struct virtio_i2c *vi2c) +{ + vi2c->vdev->config->del_vqs(vi2c->vdev); +} + +static int virti2c_init_hw(struct virtio_device *vdev, + struct virtio_i2c *vi2c) +{ + int err; + + i2c_set_adapdata(&vi2c->adapter, vi2c); + vi2c->adapter.algo = &virtio_i2c_algorithm; + + vi2c->adapter.owner = THIS_MODULE; + vi2c->adapter.dev.parent = &vdev->dev; + vi2c->adapter.dev.of_node = NULL; + + /* read virtio i2c config info */ + vi2c->adapter.nr = virtio_cread32(vdev, I2C_ADAPTER_NR); + snprintf(vi2c->adapter.name, sizeof(vi2c->adapter.name), + "virtio_i2c_%d", vi2c->adapter.nr); + + err = i2c_add_numbered_adapter(&vi2c->adapter); + if (err) + return err; + return 0; +} + +static int virti2c_probe(struct virtio_device *vdev) +{ + struct virtio_i2c *vi2c; + int err = 0; + + if (!virtio_has_feature(vdev, VIRTIO_F_VERSION_1)) + return -ENODEV; + + vi2c = kzalloc(sizeof(*vi2c), GFP_KERNEL); + if (!vi2c) + return -ENOMEM; + + vi2c->vdev = vdev; + vdev->priv = vi2c; + init_waitqueue_head(&vi2c->inq); + + err = virti2c_init_vqs(vi2c); + if (err) + goto err_init_vq; + + err = virti2c_init_hw(vdev, vi2c); + if (err) + goto err_init_hw; + + virtio_device_ready(vdev); + + virtqueue_enable_cb(vi2c->vq); + return 0; + +err_init_hw: + virti2c_del_vqs(vi2c); +err_init_vq: + kfree(vi2c); + return err; +} +static void virti2c_remove(struct virtio_device *vdev) +{ + struct virtio_i2c *vi2c = vdev->priv; + + i2c_del_adapter(&vi2c->adapter); + vdev->config->reset(vdev); + virti2c_del_vqs(vi2c); + kfree(vi2c); +} + +static unsigned int features[] = { + /* none */ +}; +static struct virtio_device_id id_table[] = { + { VIRTIO_ID_I2C, VIRTIO_DEV_ANY_ID }, + { 0 }, +}; + +static struct virtio_driver virtio_i2c_driver = { + .driver.name = KBUILD_MODNAME, + .driver.owner = THIS_MODULE, + .feature_table = features, + .feature_table_size = ARRAY_SIZE(features), + .id_table = id_table, + .probe = virti2c_probe, + .remove = virti2c_remove, +}; + +module_virtio_driver(virtio_i2c_driver); +MODULE_DEVICE_TABLE(virtio, id_table); + +MODULE_DESCRIPTION("Virtio i2c frontend driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/media/platform/msm/camera_v2/isp/msm_isp.c b/drivers/media/platform/msm/camera_v2/isp/msm_isp.c index fa5aee08c37d..c0bf239e0a35 100644 --- a/drivers/media/platform/msm/camera_v2/isp/msm_isp.c +++ b/drivers/media/platform/msm/camera_v2/isp/msm_isp.c @@ -35,6 +35,7 @@ #include "msm_isp44.h" #include "msm_isp40.h" #include "msm_isp32.h" +#include "msm_cam_cx_ipeak.h" static struct msm_sd_req_vb2_q vfe_vb2_ops; static struct msm_isp_buf_mgr vfe_buf_mgr; @@ -675,6 +676,11 @@ int vfe_hw_probe(struct platform_device *pdev) "qcom,vfe-cx-ipeak", NULL)) { vfe_dev->vfe_cx_ipeak = cx_ipeak_register( pdev->dev.of_node, "qcom,vfe-cx-ipeak"); + if (vfe_dev->vfe_cx_ipeak) + cam_cx_ipeak_register_cx_ipeak( + vfe_dev->vfe_cx_ipeak, &vfe_dev->cx_ipeak_bit); + pr_debug("%s: register cx_ipeak received bit %d\n", + __func__, vfe_dev->cx_ipeak_bit); } } else { vfe_dev->hw_info = (struct msm_vfe_hardware_info *) diff --git a/drivers/media/platform/msm/camera_v2/isp/msm_isp.h b/drivers/media/platform/msm/camera_v2/isp/msm_isp.h index 0c18bfe55842..9bc6fde36774 100644 --- a/drivers/media/platform/msm/camera_v2/isp/msm_isp.h +++ b/drivers/media/platform/msm/camera_v2/isp/msm_isp.h @@ -791,6 +791,7 @@ struct vfe_device { enum cam_ahb_clk_vote ahb_vote; enum cam_ahb_clk_vote user_requested_ahb_vote; struct cx_ipeak_client *vfe_cx_ipeak; + int cx_ipeak_bit; /* Sync variables*/ struct completion reset_complete; diff --git a/drivers/media/platform/msm/camera_v2/isp/msm_isp47.c b/drivers/media/platform/msm/camera_v2/isp/msm_isp47.c index 0daf2d914be5..a8341a7ff3e6 100644 --- a/drivers/media/platform/msm/camera_v2/isp/msm_isp47.c +++ b/drivers/media/platform/msm/camera_v2/isp/msm_isp47.c @@ -25,6 +25,7 @@ #include "cam_soc_api.h" #include "msm_isp48.h" #include "linux/iopoll.h" +#include "msm_cam_cx_ipeak.h" #undef CDBG #define CDBG(fmt, args...) pr_debug(fmt, ##args) @@ -2680,7 +2681,9 @@ int msm_vfe47_set_clk_rate(struct vfe_device *vfe_dev, long *rate) prev_clk_rate < vfe_dev->vfe_clk_rates[MSM_VFE_CLK_RATE_NOMINAL] [vfe_dev->hw_info->vfe_clk_idx]) { - ret = cx_ipeak_update(vfe_dev->vfe_cx_ipeak, true); + pr_debug("%s: clk is more than Nominal vfe %d, ipeak bit %d\n", + __func__, vfe_dev->pdev->id, vfe_dev->cx_ipeak_bit); + ret = cam_cx_ipeak_update_vote_cx_ipeak(vfe_dev->cx_ipeak_bit); if (ret) { pr_err("%s: cx_ipeak_update failed %d\n", __func__, ret); @@ -2703,7 +2706,9 @@ int msm_vfe47_set_clk_rate(struct vfe_device *vfe_dev, long *rate) prev_clk_rate >= vfe_dev->vfe_clk_rates[MSM_VFE_CLK_RATE_NOMINAL] [vfe_dev->hw_info->vfe_clk_idx]) { - ret = cx_ipeak_update(vfe_dev->vfe_cx_ipeak, false); + pr_debug("%s:clk is less than Nominal vfe %d, ipeak bit %d\n", + __func__, vfe_dev->pdev->id, vfe_dev->cx_ipeak_bit); + ret = cam_cx_ipeak_unvote_cx_ipeak(vfe_dev->cx_ipeak_bit); if (ret) { pr_err("%s: cx_ipeak_update failed %d\n", __func__, ret); diff --git a/drivers/mmc/core/core.c b/drivers/mmc/core/core.c index 76dbbbde884b..db4f4c8638b4 100644 --- a/drivers/mmc/core/core.c +++ b/drivers/mmc/core/core.c @@ -285,6 +285,7 @@ static int mmc_devfreq_get_dev_status(struct device *dev, { struct mmc_host *host = container_of(dev, struct mmc_host, class_dev); struct mmc_devfeq_clk_scaling *clk_scaling; + bool disable = false; if (!host) { pr_err("bad host parameter\n"); @@ -312,7 +313,14 @@ static int mmc_devfreq_get_dev_status(struct device *dev, } } - status->busy_time = clk_scaling->total_busy_time_us; + if (host->ops->check_temp && + host->card->clk_scaling_highest > UHS_DDR50_MAX_DTR) + disable = host->ops->check_temp(host); + /* busy_time=0 for running at low freq*/ + if (disable) + status->busy_time = 0; + else + status->busy_time = clk_scaling->total_busy_time_us; status->total_time = ktime_to_us(ktime_sub(ktime_get(), clk_scaling->measure_interval_start)); clk_scaling->total_busy_time_us = 0; diff --git a/drivers/mmc/core/sd.c b/drivers/mmc/core/sd.c index bf896b605487..9a7f9d27be1f 100644 --- a/drivers/mmc/core/sd.c +++ b/drivers/mmc/core/sd.c @@ -33,6 +33,8 @@ #define UHS_SDR25_MIN_DTR (25 * 1000 * 1000) #define UHS_SDR12_MIN_DTR (12.5 * 1000 * 1000) +#define ENOCALLBACK 1 + static const unsigned int tran_exp[] = { 10000, 100000, 1000000, 10000000, 0, 0, 0, 0 @@ -498,7 +500,11 @@ static int sd_set_bus_speed_mode(struct mmc_card *card, u8 *status) err = -EBUSY; } else { mmc_set_timing(card->host, timing); - mmc_set_clock(card->host, card->sw_caps.uhs_max_dtr); + if (card->host->ops->check_temp(card->host) && + timing == MMC_TIMING_UHS_SDR104) + mmc_set_clock(card->host, UHS_SDR50_MAX_DTR); + else + mmc_set_clock(card->host, card->sw_caps.uhs_max_dtr); } return err; @@ -1122,6 +1128,34 @@ free_card: return err; } +static int mmc_sd_init_temp_control_clk_scaling(struct mmc_host *host) +{ + int ret; + + if (host->ops->reg_temp_callback) { + ret = host->ops->reg_temp_callback(host); + } else { + pr_err("%s: %s: couldn't find init temp control clk scaling cb\n", + mmc_hostname(host), __func__); + ret = -ENOCALLBACK; + } + return ret; +} + +static int mmc_sd_dereg_temp_control_clk_scaling(struct mmc_host *host) +{ + int ret; + + if (host->ops->dereg_temp_callback) { + ret = host->ops->dereg_temp_callback(host); + } else { + pr_err("%s: %s: couldn't find dereg temp control clk scaling cb\n", + mmc_hostname(host), __func__); + ret = -ENOCALLBACK; + } + return ret; +} + /* * Host is being removed. Free up the current card. */ @@ -1131,6 +1165,7 @@ static void mmc_sd_remove(struct mmc_host *host) BUG_ON(!host->card); mmc_exit_clk_scaling(host); + mmc_sd_dereg_temp_control_clk_scaling(host); mmc_remove_card(host->card); mmc_claim_host(host); @@ -1458,6 +1493,9 @@ int mmc_attach_sd(struct mmc_host *host) goto err; } + if (mmc_sd_init_temp_control_clk_scaling(host)) + pr_err("%s: failed to init temp control clk scaling\n", + mmc_hostname(host)); /* * Detect and init the card. */ diff --git a/drivers/mmc/host/sdhci-msm.c b/drivers/mmc/host/sdhci-msm.c index 81a781c1f9d6..0468ea464055 100644 --- a/drivers/mmc/host/sdhci-msm.c +++ b/drivers/mmc/host/sdhci-msm.c @@ -40,6 +40,8 @@ #include <linux/pm_runtime.h> #include <trace/events/mmc.h> #include <soc/qcom/boot_stats.h> +#include <linux/msm_thermal.h> +#include <linux/msm_tsens.h> #include "sdhci-msm.h" #include "sdhci-msm-ice.h" @@ -169,6 +171,8 @@ #define MAX_DRV_TYPES_SUPPORTED_HS200 4 #define MSM_AUTOSUSPEND_DELAY_MS 100 +#define CENTI_DEGREE_TO_DEGREE 10 + struct sdhci_msm_offset { u32 CORE_MCI_DATA_CNT; u32 CORE_MCI_STATUS; @@ -3534,6 +3538,151 @@ int sdhci_msm_notify_load(struct sdhci_host *host, enum mmc_load state) return 0; } +static void sdhci_msm_tsens_threshold_notify( + struct therm_threshold *tsens_cb_data) +{ + struct threshold_info *info = tsens_cb_data->parent; + struct sdhci_msm_host *msm_host = container_of(info, + struct sdhci_msm_host, tsens_threshold_config); + int ret = 0; + + pr_debug("%s: Triggered tsens-notification type=%d zone_id =%d\n", + mmc_hostname(msm_host->mmc), tsens_cb_data->trip_triggered, + tsens_cb_data->sensor_id); + + switch (tsens_cb_data->trip_triggered) { + case THERMAL_TRIP_CONFIGURABLE_HI: + atomic_set(&msm_host->clk_scaling_disable, 0); + break; + case THERMAL_TRIP_CONFIGURABLE_LOW: + atomic_set(&msm_host->clk_scaling_disable, 1); + break; + default: + pr_err("%s: trip type %d not supported\n", + mmc_hostname(msm_host->mmc), + tsens_cb_data->trip_triggered); + break; + } + + ret = sensor_mgr_set_threshold(tsens_cb_data->sensor_id, + tsens_cb_data->threshold); + if (ret < 0) + pr_err("%s: failed to set threshold temp, ret==%d\n", + __func__, ret); +} + +static int sdhci_msm_check_tsens(struct sdhci_msm_host *msm_host) +{ + int ret = 0; + int temp = 0; + bool disable; + struct tsens_device tsens_dev; + + if (tsens_is_ready() > 0) { + tsens_dev.sensor_num = msm_host->tsens_id; + ret = tsens_get_temp(&tsens_dev, &temp); + if (ret < 0) { + pr_err("%s: failed to read tsens, ret = %d\n", + mmc_hostname(msm_host->mmc), ret); + return ret; + } + /* convert centidegree to degree*/ + temp /= CENTI_DEGREE_TO_DEGREE; + disable = temp <= msm_host->disable_scaling_threshold_temp; + if (disable) + atomic_set(&msm_host->clk_scaling_disable, 1); + } + return ret; +} + +static int sdhci_msm_register_cb(struct sdhci_msm_host *msm_host) +{ + int ret; + + ret = sdhci_msm_check_tsens(msm_host); + if (ret) { + pr_err("%s: unable to check tsens\n", + mmc_hostname(msm_host->mmc)); + return ret; + } + + ret = sensor_mgr_init_threshold(&msm_host->tsens_threshold_config, + msm_host->tsens_id, + msm_host->enable_scaling_threshold_temp,/*high*/ + msm_host->disable_scaling_threshold_temp,/*low*/ + sdhci_msm_tsens_threshold_notify); + if (ret) { + pr_err("%s: failed to register cb for tsens, ret = %d\n", + mmc_hostname(msm_host->mmc), ret); + return ret; + } + + ret = sensor_mgr_convert_id_and_set_threshold( + &msm_host->tsens_threshold_config); + if (ret) { + pr_err("%s: failed to set tsens threshold, ret = %d\n", + mmc_hostname(msm_host->mmc), ret); + return ret; + } + return ret; +} + +static int sdhci_msm_tsens_pltfm_init(struct sdhci_msm_host *msm_host) +{ + int ret = 0; + struct device *dev = &msm_host->pdev->dev; + struct device_node *np = dev->of_node; + + of_property_read_u32(np, "qcom,tsens-id", &msm_host->tsens_id); + of_property_read_s32(np, "qcom,disable_scaling_threshold_temp", + &msm_host->disable_scaling_threshold_temp); + of_property_read_s32(np, "qcom,enable_scaling_threshold_temp", + &msm_host->enable_scaling_threshold_temp); + + if (msm_host->tsens_id) + msm_host->temp_control_scaling = true; + else + msm_host->temp_control_scaling = false; + + atomic_set(&msm_host->clk_scaling_disable, 0); + return ret; +} + +static int sdhci_msm_dereg_temp_callback(struct sdhci_host *host) +{ + struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host); + struct sdhci_msm_host *msm_host = pltfm_host->priv; + + if (msm_host->temp_control_scaling) + sensor_mgr_remove_threshold( + &msm_host->tsens_threshold_config); + return 0; +} + +static int sdhci_msm_reg_temp_callback(struct sdhci_host *host) +{ + struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host); + struct sdhci_msm_host *msm_host = pltfm_host->priv; + int ret = 0; + + if (msm_host->temp_control_scaling) { + ret = sdhci_msm_register_cb(msm_host); + if (ret) + pr_err("%s: failed register temp monitoring call back, ret = %d\n", + mmc_hostname(msm_host->mmc), ret); + } + return ret; +} + +static int sdhci_msm_check_temp(struct sdhci_host *host) +{ + struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host); + struct sdhci_msm_host *msm_host = pltfm_host->priv; + + return atomic_read(&msm_host->clk_scaling_disable); +} + + void sdhci_msm_reset_workaround(struct sdhci_host *host, u32 enable) { u32 vendor_func2; @@ -4080,6 +4229,9 @@ static struct sdhci_ops sdhci_msm_ops = { .clear_set_dumpregs = sdhci_msm_clear_set_dumpregs, .enhanced_strobe_mask = sdhci_msm_enhanced_strobe_mask, .notify_load = sdhci_msm_notify_load, + .check_temp = sdhci_msm_check_temp, + .reg_temp_callback = sdhci_msm_reg_temp_callback, + .dereg_temp_callback = sdhci_msm_dereg_temp_callback, .reset_workaround = sdhci_msm_reset_workaround, .init = sdhci_msm_init, .pre_req = sdhci_msm_pre_req, @@ -4650,6 +4802,7 @@ static int sdhci_msm_probe(struct platform_device *pdev) MMC_CAP2_PACKED_WR_CONTROL); } + sdhci_msm_tsens_pltfm_init(msm_host); init_completion(&msm_host->pwr_irq_completion); if (gpio_is_valid(msm_host->pdata->status_gpio)) { @@ -5073,5 +5226,30 @@ static struct platform_driver sdhci_msm_driver = { module_platform_driver(sdhci_msm_driver); +static const struct of_device_id late_sdhci_msm_dt_match[] = { + {.compatible = "qcom,late-sdhci-msm"}, + {.compatible = "qcom,sdhci-msm-v5"}, + {}, +}; +MODULE_DEVICE_TABLE(of, late_sdhci_msm_dt_match); + +static struct platform_driver late_sdhci_msm_driver = { + .probe = sdhci_msm_probe, + .remove = sdhci_msm_remove, + .driver = { + .name = "late_sdhci_msm", + .owner = THIS_MODULE, + .probe_type = PROBE_PREFER_ASYNCHRONOUS, + .of_match_table = late_sdhci_msm_dt_match, + .pm = SDHCI_MSM_PMOPS, + }, +}; + +static int __init late_sdhci_msm_init_driver(void) +{ + return platform_driver_register(&late_sdhci_msm_driver); +} +late_initcall(late_sdhci_msm_init_driver); + MODULE_DESCRIPTION("Qualcomm Technologies, Inc. Secure Digital Host Controller Interface driver"); MODULE_LICENSE("GPL v2"); diff --git a/drivers/mmc/host/sdhci-msm.h b/drivers/mmc/host/sdhci-msm.h index 79949c2c537f..bb15cd58c664 100644 --- a/drivers/mmc/host/sdhci-msm.h +++ b/drivers/mmc/host/sdhci-msm.h @@ -1,5 +1,5 @@ /* - * Copyright (c) 2016-2017, The Linux Foundation. All rights reserved. + * Copyright (c) 2016-2018, 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 @@ -17,6 +17,8 @@ #include <linux/mmc/mmc.h> #include <linux/pm_qos.h> +#include <linux/msm_thermal.h> +#include <linux/msm_tsens.h> #include "sdhci-pltfm.h" /* This structure keeps information per regulator */ @@ -227,6 +229,14 @@ struct sdhci_msm_host { const struct sdhci_msm_offset *offset; bool core_3_0v_support; bool pltfm_init_done; + + /* temperature controlled scaling */ + int tsens_id; + int disable_scaling_threshold_temp; + int enable_scaling_threshold_temp; + bool temp_control_scaling; + atomic_t clk_scaling_disable; + struct threshold_info tsens_threshold_config; }; extern char *saved_command_line; diff --git a/drivers/mmc/host/sdhci.c b/drivers/mmc/host/sdhci.c index 40a08a520861..b96f27f861ba 100644 --- a/drivers/mmc/host/sdhci.c +++ b/drivers/mmc/host/sdhci.c @@ -1583,6 +1583,27 @@ static void sdhci_notify_halt(struct mmc_host *mmc, bool halt) } } +static int sdhci_reg_temp_callback(struct mmc_host *mmc) +{ + struct sdhci_host *host = mmc_priv(mmc); + + return host->ops->reg_temp_callback(host); +} + +static int sdhci_check_temp(struct mmc_host *mmc) +{ + struct sdhci_host *host = mmc_priv(mmc); + + return host->ops->check_temp(host); +} + +static int sdhci_dereg_temp_callback(struct mmc_host *mmc) +{ + struct sdhci_host *host = mmc_priv(mmc); + + return host->ops->dereg_temp_callback(host); +} + static inline void sdhci_update_power_policy(struct sdhci_host *host, enum sdhci_power_policy policy) { @@ -2769,6 +2790,9 @@ static const struct mmc_host_ops sdhci_ops = { .notify_load = sdhci_notify_load, .notify_halt = sdhci_notify_halt, .force_err_irq = sdhci_force_err_irq, + .check_temp = sdhci_check_temp, + .dereg_temp_callback = sdhci_dereg_temp_callback, + .reg_temp_callback = sdhci_reg_temp_callback, }; /*****************************************************************************\ diff --git a/drivers/mmc/host/sdhci.h b/drivers/mmc/host/sdhci.h index 81aecb90ac8d..86b7066a81c2 100644 --- a/drivers/mmc/host/sdhci.h +++ b/drivers/mmc/host/sdhci.h @@ -691,6 +691,9 @@ struct sdhci_ops { unsigned int max_dtr, int host_drv, int card_drv, int *drv_type); int (*notify_load)(struct sdhci_host *host, enum mmc_load state); + int (*check_temp)(struct sdhci_host *host); + int (*reg_temp_callback)(struct sdhci_host *host); + int (*dereg_temp_callback)(struct sdhci_host *host); void (*reset_workaround)(struct sdhci_host *host, u32 enable); void (*init)(struct sdhci_host *host); void (*pre_req)(struct sdhci_host *host, struct mmc_request *req); diff --git a/include/linux/mmc/host.h b/include/linux/mmc/host.h index 48849acf34ff..49648aa63ee3 100644 --- a/include/linux/mmc/host.h +++ b/include/linux/mmc/host.h @@ -185,6 +185,9 @@ struct mmc_host_ops { int (*notify_load)(struct mmc_host *, enum mmc_load); void (*notify_halt)(struct mmc_host *mmc, bool halt); void (*force_err_irq)(struct mmc_host *host, u64 errmask); + int (*check_temp)(struct mmc_host *host); + int (*reg_temp_callback)(struct mmc_host *host); + int (*dereg_temp_callback)(struct mmc_host *host); }; struct mmc_card; diff --git a/include/uapi/linux/virtio_ids.h b/include/uapi/linux/virtio_ids.h index 77925f587b15..331bf78f6497 100644 --- a/include/uapi/linux/virtio_ids.h +++ b/include/uapi/linux/virtio_ids.h @@ -42,4 +42,7 @@ #define VIRTIO_ID_GPU 16 /* virtio GPU */ #define VIRTIO_ID_INPUT 18 /* virtio input */ +/* QCOM Private virtio device IDs */ +#define VIRTIO_ID_I2C 21 /* virtio i2c */ + #endif /* _LINUX_VIRTIO_IDS_H */ |
