summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--Documentation/devicetree/bindings/mmc/sdhci-msm.txt12
-rw-r--r--arch/arm/boot/dts/qcom/msm8996pro-auto-adp.dts9
-rw-r--r--drivers/i2c/busses/Kconfig6
-rw-r--r--drivers/i2c/busses/Makefile1
-rw-r--r--drivers/i2c/busses/virtio-i2c.c356
-rw-r--r--drivers/media/platform/msm/camera_v2/isp/msm_isp.c6
-rw-r--r--drivers/media/platform/msm/camera_v2/isp/msm_isp.h1
-rw-r--r--drivers/media/platform/msm/camera_v2/isp/msm_isp47.c9
-rw-r--r--drivers/mmc/core/core.c10
-rw-r--r--drivers/mmc/core/sd.c40
-rw-r--r--drivers/mmc/host/sdhci-msm.c178
-rw-r--r--drivers/mmc/host/sdhci-msm.h12
-rw-r--r--drivers/mmc/host/sdhci.c24
-rw-r--r--drivers/mmc/host/sdhci.h3
-rw-r--r--include/linux/mmc/host.h3
-rw-r--r--include/uapi/linux/virtio_ids.h3
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 */