summaryrefslogtreecommitdiff
path: root/drivers
diff options
context:
space:
mode:
authorMark Brown <broonie@kernel.org>2026-07-23 15:40:33 +0100
committerMark Brown <broonie@kernel.org>2026-07-23 15:40:35 +0100
commite8214d59a905fa2b8447481feb02aff44b05524f (patch)
treea4e2333ab3b44d312c09dbf077f9f0068713673b /drivers
parent329757ecbd8952d6c819e81bae77dc8cb2333047 (diff)
parentaec9f1f18ffdda04b4d68ddadbc3d893eb3a9cbb (diff)
downloadlinux-next-e8214d59a905fa2b8447481feb02aff44b05524f.tar.gz
linux-next-e8214d59a905fa2b8447481feb02aff44b05524f.zip
Merge branch 'for-next' of https://git.kernel.org/pub/scm/linux/kernel/git/mkp/scsi.git
Diffstat (limited to 'drivers')
-rw-r--r--drivers/parisc/superio.c2
-rw-r--r--drivers/scsi/mpt3sas/Makefile2
-rw-r--r--drivers/scsi/mpt3sas/mpt3sas_base.h20
-rw-r--r--drivers/scsi/mpt3sas/mpt3sas_config.c36
-rw-r--r--drivers/scsi/mpt3sas/mpt3sas_hwmon.c191
-rw-r--r--drivers/scsi/mpt3sas/mpt3sas_scsih.c6
-rw-r--r--drivers/scsi/qla2xxx/qla_os.c15
-rw-r--r--drivers/scsi/virtio_scsi.c18
-rw-r--r--drivers/ufs/core/ufs-sysfs.c1
-rw-r--r--drivers/ufs/core/ufs-txeq.c73
-rw-r--r--drivers/ufs/core/ufshcd.c23
-rw-r--r--drivers/ufs/host/ufs-mediatek.c12
-rw-r--r--drivers/ufs/host/ufs-mediatek.h4
-rw-r--r--drivers/ufs/host/ufs-qcom.c9
-rw-r--r--drivers/ufs/host/ufshcd-pltfrm.c154
15 files changed, 515 insertions, 51 deletions
diff --git a/drivers/parisc/superio.c b/drivers/parisc/superio.c
index e973c6893203..3aaab40df86e 100644
--- a/drivers/parisc/superio.c
+++ b/drivers/parisc/superio.c
@@ -15,7 +15,7 @@
* (C) Copyright 2005 Kyle McMartin <kyle@parisc-linux.org>
* (C) Copyright 2006 Helge Deller <deller@gmx.de>
*
- * The initial version of this is by Martin Peterson. Alex deVries
+ * The initial version of this is by Martin Petersen. Alex deVries
* has spent a bit of time trying to coax it into working.
*
* Major changes to get basic interrupt infrastructure working to
diff --git a/drivers/scsi/mpt3sas/Makefile b/drivers/scsi/mpt3sas/Makefile
index e76d994dbed3..18e2d87eb4a2 100644
--- a/drivers/scsi/mpt3sas/Makefile
+++ b/drivers/scsi/mpt3sas/Makefile
@@ -9,3 +9,5 @@ mpt3sas-y += mpt3sas_base.o \
mpt3sas_trigger_diag.o \
mpt3sas_warpdrive.o \
mpt3sas_debugfs.o \
+
+mpt3sas-$(CONFIG_HWMON) += mpt3sas_hwmon.o
diff --git a/drivers/scsi/mpt3sas/mpt3sas_base.h b/drivers/scsi/mpt3sas/mpt3sas_base.h
index d4597d058705..47255bf9cdda 100644
--- a/drivers/scsi/mpt3sas/mpt3sas_base.h
+++ b/drivers/scsi/mpt3sas/mpt3sas_base.h
@@ -1629,6 +1629,9 @@ struct MPT3SAS_ADAPTER {
u8 is_aero_ioc;
struct dentry *debugfs_root;
struct dentry *ioc_dump;
+#if IS_REACHABLE(CONFIG_HWMON)
+ struct mpt3sas_hwmon *hwmon;
+#endif
PUT_SMID_IO_FP_HIP put_smid_scsi_io;
PUT_SMID_IO_FP_HIP put_smid_fast_path;
PUT_SMID_IO_FP_HIP put_smid_hi_priority;
@@ -1904,6 +1907,9 @@ int mpt3sas_config_get_iounit_pg3(struct MPT3SAS_ADAPTER *ioc,
Mpi2ConfigReply_t *mpi_reply, Mpi2IOUnitPage3_t *config_page, u16 sz);
int mpt3sas_config_set_iounit_pg1(struct MPT3SAS_ADAPTER *ioc, Mpi2ConfigReply_t
*mpi_reply, Mpi2IOUnitPage1_t *config_page);
+int mpt3sas_config_get_iounit_pg7(struct MPT3SAS_ADAPTER *ioc,
+ Mpi2ConfigReply_t *mpi_reply,
+ Mpi2IOUnitPage7_t *config_page);
int mpt3sas_config_get_iounit_pg8(struct MPT3SAS_ADAPTER *ioc, Mpi2ConfigReply_t
*mpi_reply, Mpi2IOUnitPage8_t *config_page);
int mpt3sas_config_get_sas_iounit_pg1(struct MPT3SAS_ADAPTER *ioc,
@@ -2047,6 +2053,20 @@ void mpt3sas_destroy_debugfs(struct MPT3SAS_ADAPTER *ioc);
void mpt3sas_init_debugfs(void);
void mpt3sas_exit_debugfs(void);
+#if IS_REACHABLE(CONFIG_HWMON)
+int mpt3sas_hwmon_register(struct MPT3SAS_ADAPTER *ioc);
+void mpt3sas_hwmon_unregister(struct MPT3SAS_ADAPTER *ioc);
+#else
+static inline int mpt3sas_hwmon_register(struct MPT3SAS_ADAPTER *ioc)
+{
+ return 0;
+}
+
+static inline void mpt3sas_hwmon_unregister(struct MPT3SAS_ADAPTER *ioc)
+{
+}
+#endif
+
/**
* _scsih_is_pcie_scsi_device - determines if device is an pcie scsi device
* @device_info: bitfield providing information about the device.
diff --git a/drivers/scsi/mpt3sas/mpt3sas_config.c b/drivers/scsi/mpt3sas/mpt3sas_config.c
index 45ac853e1289..b0d5ef893600 100644
--- a/drivers/scsi/mpt3sas/mpt3sas_config.c
+++ b/drivers/scsi/mpt3sas/mpt3sas_config.c
@@ -992,6 +992,42 @@ mpt3sas_config_get_iounit_pg3(struct MPT3SAS_ADAPTER *ioc,
}
/**
+ * mpt3sas_config_get_iounit_pg7 - obtain iounit page 7
+ * @ioc: per adapter object
+ * @mpi_reply: reply mf payload returned from firmware
+ * @config_page: contents of the config page
+ * Context: sleep.
+ *
+ * Return: 0 for success, non-zero for failure.
+ */
+int mpt3sas_config_get_iounit_pg7(struct MPT3SAS_ADAPTER *ioc,
+ Mpi2ConfigReply_t *mpi_reply,
+ Mpi2IOUnitPage7_t *config_page)
+{
+ Mpi2ConfigRequest_t mpi_request;
+ int r;
+
+ memset(&mpi_request, 0, sizeof(Mpi2ConfigRequest_t));
+ mpi_request.Function = MPI2_FUNCTION_CONFIG;
+ mpi_request.Action = MPI2_CONFIG_ACTION_PAGE_HEADER;
+ mpi_request.Header.PageType = MPI2_CONFIG_PAGETYPE_IO_UNIT;
+ mpi_request.Header.PageNumber = 7;
+ mpi_request.Header.PageVersion = MPI2_IOUNITPAGE7_PAGEVERSION;
+ ioc->build_zero_len_sge_mpi(ioc, &mpi_request.PageBufferSGE);
+ r = _config_request(ioc, &mpi_request, mpi_reply,
+ MPT3_CONFIG_PAGE_DEFAULT_TIMEOUT, NULL, 0);
+ if (r)
+ goto out;
+
+ mpi_request.Action = MPI2_CONFIG_ACTION_PAGE_READ_CURRENT;
+ r = _config_request(ioc, &mpi_request, mpi_reply,
+ MPT3_CONFIG_PAGE_DEFAULT_TIMEOUT,
+ config_page, sizeof(*config_page));
+ out:
+ return r;
+}
+
+/**
* mpt3sas_config_get_iounit_pg8 - obtain iounit page 8
* @ioc: per adapter object
* @mpi_reply: reply mf payload returned from firmware
diff --git a/drivers/scsi/mpt3sas/mpt3sas_hwmon.c b/drivers/scsi/mpt3sas/mpt3sas_hwmon.c
new file mode 100644
index 000000000000..297a8efecadb
--- /dev/null
+++ b/drivers/scsi/mpt3sas/mpt3sas_hwmon.c
@@ -0,0 +1,191 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Hardware monitoring (hwmon) support for the LSI / Broadcom mpt3sas
+ * SAS HBA driver. Exposes the IOC and board temperature sensors by
+ * reading MPI IO Unit Page 7.
+ */
+
+#include <linux/err.h>
+#include <linux/hwmon.h>
+#include <linux/slab.h>
+
+#include "mpt3sas_base.h"
+
+struct mpt3sas_hwmon {
+ struct MPT3SAS_ADAPTER *ioc;
+ struct device *hwmon_dev;
+ bool ioc_present;
+ bool board_present;
+};
+
+/*
+ * Convert a (raw, units) reading to millidegrees Celsius.
+ * Returns -ENODATA when the sensor reports "not present" or
+ * unknown units. Temperature values are interpreted as signed
+ * two's-complement integers.
+ *
+ * The MPI2_IOUNITPAGE7_IOC_TEMP_* and MPI2_IOUNITPAGE7_BOARD_TEMP_*
+ * defines in mpi2_cnfg.h share the same values; the IOC ones are
+ * used for both channels.
+ */
+static int _hwmon_to_mdegc(s16 raw, u8 units, long *out)
+{
+ switch (units) {
+ case MPI2_IOUNITPAGE7_IOC_TEMP_CELSIUS:
+ *out = (long)raw * 1000;
+ return 0;
+ case MPI2_IOUNITPAGE7_IOC_TEMP_FAHRENHEIT:
+ /* (F - 32) * 5 / 9, expressed in milli-units */
+ *out = ((long)raw - 32) * 5000 / 9;
+ return 0;
+ default:
+ return -ENODATA;
+ }
+}
+
+static umode_t _hwmon_is_visible(const void *drvdata,
+ enum hwmon_sensor_types type,
+ u32 attr, int channel)
+{
+ const struct mpt3sas_hwmon *h = drvdata;
+
+ if (type != hwmon_temp)
+ return 0;
+ if (attr != hwmon_temp_input && attr != hwmon_temp_label)
+ return 0;
+ if (channel == 0 && h->ioc_present)
+ return 0444;
+ if (channel == 1 && h->board_present)
+ return 0444;
+ return 0;
+}
+
+static int _hwmon_read(struct device *dev, enum hwmon_sensor_types type,
+ u32 attr, int channel, long *val)
+{
+ struct mpt3sas_hwmon *h = dev_get_drvdata(dev);
+ Mpi2ConfigReply_t mpi_reply;
+ Mpi2IOUnitPage7_t page;
+ int r;
+
+ if (type != hwmon_temp || attr != hwmon_temp_input)
+ return -EOPNOTSUPP;
+
+ r = mpt3sas_config_get_iounit_pg7(h->ioc, &mpi_reply, &page);
+ if (r)
+ return r;
+
+ if (channel == 0)
+ return _hwmon_to_mdegc((s16)le16_to_cpu(page.IOCTemperature),
+ page.IOCTemperatureUnits, val);
+ if (channel == 1)
+ return _hwmon_to_mdegc((s16)le16_to_cpu(page.BoardTemperature),
+ page.BoardTemperatureUnits, val);
+ return -EOPNOTSUPP;
+}
+
+static const char * const mpt3sas_hwmon_temp_labels[] = {
+ "IOC",
+ "Board",
+};
+
+static int _hwmon_read_string(struct device *dev,
+ enum hwmon_sensor_types type,
+ u32 attr, int channel, const char **str)
+{
+ if (type != hwmon_temp || attr != hwmon_temp_label)
+ return -EOPNOTSUPP;
+ *str = mpt3sas_hwmon_temp_labels[channel];
+ return 0;
+}
+
+static const struct hwmon_channel_info * const mpt3sas_hwmon_info[] = {
+ HWMON_CHANNEL_INFO(temp,
+ HWMON_T_INPUT | HWMON_T_LABEL,
+ HWMON_T_INPUT | HWMON_T_LABEL),
+ NULL,
+};
+
+static const struct hwmon_ops mpt3sas_hwmon_ops = {
+ .is_visible = _hwmon_is_visible,
+ .read = _hwmon_read,
+ .read_string = _hwmon_read_string,
+};
+
+static const struct hwmon_chip_info mpt3sas_hwmon_chip_info = {
+ .ops = &mpt3sas_hwmon_ops,
+ .info = mpt3sas_hwmon_info,
+};
+
+/**
+ * mpt3sas_hwmon_register - register an hwmon device for the IOC
+ * @ioc: per adapter object
+ * Context: sleep.
+ *
+ * Succeeds without registering when no temperature sensors are present,
+ * so cards without thermal monitoring do not expose an empty hwmon node.
+ * Paired with mpt3sas_hwmon_unregister() from the driver's remove path.
+ *
+ * Return: 0 for success, non-zero for failure.
+ */
+int mpt3sas_hwmon_register(struct MPT3SAS_ADAPTER *ioc)
+{
+ struct device *parent = &ioc->pdev->dev;
+ struct mpt3sas_hwmon *h;
+ struct device *hwdev;
+ Mpi2ConfigReply_t mpi_reply;
+ Mpi2IOUnitPage7_t page;
+ int r;
+
+ r = mpt3sas_config_get_iounit_pg7(ioc, &mpi_reply, &page);
+ if (r)
+ return r;
+
+ /*
+ * A page where both *TemperatureUnits are NOT_PRESENT covers
+ * two cases: cards that genuinely lack sensors, and firmware
+ * errors that left the page zero-filled (the accessor mirrors
+ * _config_request() behaviour). Either way: skip registration.
+ */
+ if (page.IOCTemperatureUnits == MPI2_IOUNITPAGE7_IOC_TEMP_NOT_PRESENT &&
+ page.BoardTemperatureUnits == MPI2_IOUNITPAGE7_BOARD_TEMP_NOT_PRESENT)
+ return 0;
+
+ h = kzalloc_obj(*h);
+ if (!h)
+ return -ENOMEM;
+
+ h->ioc = ioc;
+ h->ioc_present = page.IOCTemperatureUnits != MPI2_IOUNITPAGE7_IOC_TEMP_NOT_PRESENT;
+ h->board_present = page.BoardTemperatureUnits != MPI2_IOUNITPAGE7_BOARD_TEMP_NOT_PRESENT;
+
+ hwdev = hwmon_device_register_with_info(parent, "mpt3sas", h,
+ &mpt3sas_hwmon_chip_info,
+ NULL);
+ if (IS_ERR(hwdev)) {
+ kfree(h);
+ return PTR_ERR(hwdev);
+ }
+
+ h->hwmon_dev = hwdev;
+ ioc->hwmon = h;
+ return 0;
+}
+
+/**
+ * mpt3sas_hwmon_unregister - tear down the hwmon device, if any
+ * @ioc: per adapter object
+ *
+ * Safe to call when registration was skipped (no sensors) or
+ * failed; in those cases ioc->hwmon is NULL and this is a no-op.
+ */
+void mpt3sas_hwmon_unregister(struct MPT3SAS_ADAPTER *ioc)
+{
+ struct mpt3sas_hwmon *h = ioc->hwmon;
+
+ if (!h)
+ return;
+ hwmon_device_unregister(h->hwmon_dev);
+ kfree(h);
+ ioc->hwmon = NULL;
+}
diff --git a/drivers/scsi/mpt3sas/mpt3sas_scsih.c b/drivers/scsi/mpt3sas/mpt3sas_scsih.c
index 12caffeed3a0..dea78688cc9b 100644
--- a/drivers/scsi/mpt3sas/mpt3sas_scsih.c
+++ b/drivers/scsi/mpt3sas/mpt3sas_scsih.c
@@ -12562,6 +12562,7 @@ static void scsih_remove(struct pci_dev *pdev)
/* release all the volumes */
_scsih_ir_shutdown(ioc);
mpt3sas_destroy_debugfs(ioc);
+ mpt3sas_hwmon_unregister(ioc);
sas_remove_host(shost);
list_for_each_entry_safe(raid_device, next, &ioc->raid_device_list,
list) {
@@ -13651,6 +13652,11 @@ _scsih_probe(struct pci_dev *pdev, const struct pci_device_id *id)
}
scsi_scan_host(shost);
+
+ if (mpt3sas_hwmon_register(ioc))
+ ioc_warn(ioc,
+ "hwmon registration failed; temperatures not exposed\n");
+
mpt3sas_setup_debugfs(ioc);
return 0;
out_add_shost_fail:
diff --git a/drivers/scsi/qla2xxx/qla_os.c b/drivers/scsi/qla2xxx/qla_os.c
index 72b1c28e4dae..39ee33398df2 100644
--- a/drivers/scsi/qla2xxx/qla_os.c
+++ b/drivers/scsi/qla2xxx/qla_os.c
@@ -2803,11 +2803,16 @@ qla2x00_set_isp_flags(struct qla_hw_data *ha)
else {
/* Get adapter physical port no from interrupt pin register. */
pci_read_config_byte(ha->pdev, PCI_INTERRUPT_PIN, &ha->port_no);
- if (IS_QLA25XX(ha) || IS_QLA2031(ha) ||
- IS_QLA27XX(ha) || IS_QLA28XX(ha))
- ha->port_no--;
- else
- ha->port_no = !(ha->port_no & 1);
+ if (ha->port_no == 0) {
+ /* None of INT[A|B|C|D] may be virtualized by vfio */
+ ha->port_no = PCI_FUNC(ha->pdev->devfn);
+ } else {
+ if (IS_QLA25XX(ha) || IS_QLA2031(ha) ||
+ IS_QLA27XX(ha) || IS_QLA28XX(ha))
+ ha->port_no--;
+ else
+ ha->port_no = !(ha->port_no & 1);
+ }
}
ql_dbg_pci(ql_dbg_init, ha->pdev, 0x000b,
diff --git a/drivers/scsi/virtio_scsi.c b/drivers/scsi/virtio_scsi.c
index 5fdaa71f0652..35731b18c519 100644
--- a/drivers/scsi/virtio_scsi.c
+++ b/drivers/scsi/virtio_scsi.c
@@ -122,10 +122,11 @@ static void virtscsi_complete_cmd(struct virtio_scsi *vscsi, void *buf)
struct virtio_scsi_cmd *cmd = buf;
struct scsi_cmnd *sc = cmd->sc;
struct virtio_scsi_cmd_resp *resp = &cmd->resp.cmd;
+ unsigned sense_len = virtio32_to_cpu(vscsi->vdev, resp->sense_len);
dev_dbg(&sc->device->sdev_gendev,
"cmd %p response %u status %#02x sense_len %u\n",
- sc, resp->response, resp->status, resp->sense_len);
+ sc, resp->response, resp->status, sense_len);
sc->result = resp->status;
virtscsi_compute_resid(sc, virtio32_to_cpu(vscsi->vdev, resp->resid));
@@ -166,13 +167,10 @@ static void virtscsi_complete_cmd(struct virtio_scsi *vscsi, void *buf)
break;
}
- WARN_ON(virtio32_to_cpu(vscsi->vdev, resp->sense_len) >
- VIRTIO_SCSI_SENSE_SIZE);
+ WARN_ON(sense_len > VIRTIO_SCSI_SENSE_SIZE);
if (resp->sense_len) {
memcpy(sc->sense_buffer, resp->sense,
- min_t(u32,
- virtio32_to_cpu(vscsi->vdev, resp->sense_len),
- VIRTIO_SCSI_SENSE_SIZE));
+ min_t(u32, sense_len, VIRTIO_SCSI_SENSE_SIZE));
}
scsi_done(sc);
@@ -288,8 +286,9 @@ static void virtscsi_handle_transport_reset(struct virtio_scsi *vscsi,
struct Scsi_Host *shost = virtio_scsi_host(vscsi->vdev);
unsigned int target = event->lun[1];
unsigned int lun = (event->lun[2] << 8) | event->lun[3];
+ unsigned int reason = virtio32_to_cpu(vscsi->vdev, event->reason);
- switch (virtio32_to_cpu(vscsi->vdev, event->reason)) {
+ switch (reason) {
case VIRTIO_SCSI_EVT_RESET_RESCAN:
if (lun == 0) {
scsi_scan_target(&shost->shost_gendev, 0, target,
@@ -309,7 +308,7 @@ static void virtscsi_handle_transport_reset(struct virtio_scsi *vscsi,
}
break;
default:
- pr_info("Unsupported virtio scsi event reason %x\n", event->reason);
+ pr_info("Unsupported virtio scsi event reason %x\n", reason);
}
}
@@ -409,7 +408,8 @@ static void virtscsi_handle_event(struct work_struct *work)
virtscsi_handle_param_change(vscsi, event);
break;
default:
- pr_err("Unsupported virtio scsi event %x\n", event->event);
+ pr_err("Unsupported virtio scsi event %x\n",
+ virtio32_to_cpu(vscsi->vdev, event->event));
}
virtscsi_kick_event(vscsi, event_node);
}
diff --git a/drivers/ufs/core/ufs-sysfs.c b/drivers/ufs/core/ufs-sysfs.c
index d9dc4cc3452e..63e670d1a9d9 100644
--- a/drivers/ufs/core/ufs-sysfs.c
+++ b/drivers/ufs/core/ufs-sysfs.c
@@ -54,6 +54,7 @@ static const char *ufs_hs_gear_to_string(enum ufs_hs_gear_tag gear)
case UFS_HS_G3: return "HS_GEAR3";
case UFS_HS_G4: return "HS_GEAR4";
case UFS_HS_G5: return "HS_GEAR5";
+ case UFS_HS_G6: return "HS_GEAR6";
default: return "UNKNOWN";
}
}
diff --git a/drivers/ufs/core/ufs-txeq.c b/drivers/ufs/core/ufs-txeq.c
index aa64f2bf4f1e..fa5f539632eb 100644
--- a/drivers/ufs/core/ufs-txeq.c
+++ b/drivers/ufs/core/ufs-txeq.c
@@ -10,6 +10,7 @@
#include <linux/delay.h>
#include <linux/errno.h>
#include <linux/kernel.h>
+#include <linux/sched/mm.h>
#include <ufs/ufshcd.h>
#include <ufs/unipro.h>
#include "ufshcd-priv.h"
@@ -481,7 +482,8 @@ static void ufshcd_evaluate_tx_eqtr_fom(struct ufs_hba *hba,
* @h_iter: host TX EQTR iterator data structure
* @d_iter: device TX EQTR iterator data structure
*
- * Returns 0 on success, negative error code otherwise
+ * Returns 0 on success, negative error code if get_rx_fom vops fails.
+ * RX_FOM DME get failures are logged and treated as 0 FOM for that lane.
*/
static int ufshcd_get_rx_fom(struct ufs_hba *hba,
struct ufs_pa_layer_attr *pwr_mode,
@@ -496,8 +498,12 @@ static int ufshcd_get_rx_fom(struct ufs_hba *hba,
ret = ufshcd_dme_peer_get(hba, UIC_ARG_MIB_SEL(RX_FOM,
UIC_ARG_MPHY_RX_GEN_SEL_INDEX(lane)),
&fom);
- if (ret)
- return ret;
+ if (ret) {
+ h_iter->fom[lane] = 0;
+ dev_dbg(hba->dev, "Failed to get FOM for Host TX Lane %d: %d\n",
+ lane, ret);
+ continue;
+ }
h_iter->fom[lane] = (u8)fom;
}
@@ -507,8 +513,12 @@ static int ufshcd_get_rx_fom(struct ufs_hba *hba,
ret = ufshcd_dme_get(hba, UIC_ARG_MIB_SEL(RX_FOM,
UIC_ARG_MPHY_RX_GEN_SEL_INDEX(lane)),
&fom);
- if (ret)
- return ret;
+ if (ret) {
+ d_iter->fom[lane] = 0;
+ dev_dbg(hba->dev, "Failed to get FOM for Device TX Lane %d: %d\n",
+ lane, ret);
+ continue;
+ }
d_iter->fom[lane] = (u8)fom;
}
@@ -1216,14 +1226,26 @@ static int ufshcd_tx_eqtr(struct ufs_hba *hba,
struct ufs_pa_layer_attr *pwr_mode)
{
struct ufs_pa_layer_attr old_pwr_info;
+ unsigned int noio_flag;
+ int notify_ret;
int ret;
+ /*
+ * ufshcd_tx_eqtr() is called from a power-mode-change context where
+ * I/O is suspended. Use memalloc_noio_save() to propagate GFP_NOIO
+ * to all allocations in the call tree instead of tagging each call
+ * site individually.
+ */
+ noio_flag = memalloc_noio_save();
+
if (!params->eqtr_record) {
params->eqtr_record = devm_kzalloc(hba->dev,
sizeof(*params->eqtr_record),
GFP_KERNEL);
- if (!params->eqtr_record)
- return -ENOMEM;
+ if (!params->eqtr_record) {
+ ret = -ENOMEM;
+ goto out_noio_restore;
+ }
}
memcpy(&old_pwr_info, &hba->pwr_info, sizeof(struct ufs_pa_layer_attr));
@@ -1231,23 +1253,31 @@ static int ufshcd_tx_eqtr(struct ufs_hba *hba,
ret = ufshcd_tx_eqtr_prepare(hba, pwr_mode);
if (ret) {
dev_err(hba->dev, "Failed to prepare TX EQTR: %d\n", ret);
- goto out;
+ goto out_unprepare;
}
ret = ufshcd_vops_tx_eqtr_notify(hba, PRE_CHANGE, pwr_mode);
- if (ret)
- goto out;
+ if (ret) {
+ dev_err(hba->dev, "TX EQTR PRE_CHANGE notify failed: %d\n", ret);
+ goto out_unprepare;
+ }
ret = __ufshcd_tx_eqtr(hba, params, pwr_mode);
- if (ret)
- goto out;
- ret = ufshcd_vops_tx_eqtr_notify(hba, POST_CHANGE, pwr_mode);
+ notify_ret = ufshcd_vops_tx_eqtr_notify(hba, POST_CHANGE, pwr_mode);
+ if (notify_ret)
+ dev_err(hba->dev, "TX EQTR POST_CHANGE notify failed: %d\n", notify_ret);
-out:
+ if (!ret)
+ ret = notify_ret;
+
+out_unprepare:
if (ret)
ufshcd_tx_eqtr_unprepare(hba, &old_pwr_info);
+out_noio_restore:
+ memalloc_noio_restore(noio_flag);
+
return ret;
}
@@ -1301,7 +1331,13 @@ int ufshcd_config_tx_eq_settings(struct ufs_hba *hba,
}
params = &hba->tx_eq_params[gear - 1];
- if (!params->is_valid || force_tx_eqtr) {
+ /*
+ * TX EQTR must run for the following cases:
+ * 1. TX EQ settings are invalid.
+ * 2. TX EQ settings are from Device Tree.
+ * 3. TX EQTR procedure is forced.
+ */
+ if (!params->is_valid || params->from_dt || force_tx_eqtr) {
int ret;
ret = ufshcd_tx_eqtr(hba, params, pwr_mode);
@@ -1314,6 +1350,8 @@ int ufshcd_config_tx_eq_settings(struct ufs_hba *hba,
/* Mark TX Equalization settings as valid */
params->is_valid = true;
params->is_trained = true;
+ /* TX EQTR succeeds, clear from_dt flag */
+ params->from_dt = false;
params->is_applied = false;
}
@@ -1499,6 +1537,11 @@ static void ufshcd_extract_tx_eq_settings_attrs(struct ufs_hba *hba, u8 gear)
}
params->is_valid = true;
+ /*
+ * Optimal TX EQ settings are retrieved from UFS device attributes,
+ * clear from_dt flag to avoid TX EQTR procedure.
+ */
+ params->from_dt = false;
}
void ufshcd_retrieve_tx_eq_settings(struct ufs_hba *hba)
diff --git a/drivers/ufs/core/ufshcd.c b/drivers/ufs/core/ufshcd.c
index d3044a3089b5..79331c2bd38d 100644
--- a/drivers/ufs/core/ufshcd.c
+++ b/drivers/ufs/core/ufshcd.c
@@ -1445,7 +1445,7 @@ config_pwr_mode:
*
* Return: 0 upon success; -EBUSY upon timeout.
*/
-static int ufshcd_clock_scaling_prepare(struct ufs_hba *hba, u64 timeout_us)
+static int ufshcd_clock_scaling_prepare(struct ufs_hba *hba)
{
int ret = 0;
/*
@@ -1453,16 +1453,13 @@ static int ufshcd_clock_scaling_prepare(struct ufs_hba *hba, u64 timeout_us)
* clock scaling is in progress
*/
mutex_lock(&hba->host->scan_mutex);
- blk_mq_quiesce_tagset(&hba->host->tag_set);
mutex_lock(&hba->wb_mutex);
down_write(&hba->clk_scaling_lock);
- if (!hba->clk_scaling.is_allowed ||
- ufshcd_wait_for_pending_cmds(hba, timeout_us)) {
+ if (!hba->clk_scaling.is_allowed) {
ret = -EBUSY;
up_write(&hba->clk_scaling_lock);
mutex_unlock(&hba->wb_mutex);
- blk_mq_unquiesce_tagset(&hba->host->tag_set);
mutex_unlock(&hba->host->scan_mutex);
goto out;
}
@@ -1478,7 +1475,6 @@ static void ufshcd_clock_scaling_unprepare(struct ufs_hba *hba, int err)
{
up_write(&hba->clk_scaling_lock);
mutex_unlock(&hba->wb_mutex);
- blk_mq_unquiesce_tagset(&hba->host->tag_set);
mutex_unlock(&hba->host->scan_mutex);
/* Enable Write Booster if current gear requires it else disable it */
@@ -1506,7 +1502,7 @@ static int ufshcd_devfreq_scale(struct ufs_hba *hba, unsigned long freq,
new_gear = ufshcd_vops_freq_to_gear_speed(hba, freq);
- ret = ufshcd_clock_scaling_prepare(hba, 1 * USEC_PER_SEC);
+ ret = ufshcd_clock_scaling_prepare(hba);
if (ret)
return ret;
@@ -2506,7 +2502,10 @@ static inline int ufshcd_hba_capabilities(struct ufs_hba *hba)
hba->nutmrs =
((hba->capabilities & MASK_TASK_MANAGEMENT_REQUEST_SLOTS) >> 16) + 1;
- hba->nortt = FIELD_GET(MASK_NUMBER_OUTSTANDING_RTT, hba->capabilities) + 1;
+ if (hba->vops && hba->vops->get_hba_nortt)
+ hba->nortt = hba->vops->get_hba_nortt(hba);
+ else
+ hba->nortt = FIELD_GET(MASK_NUMBER_OUTSTANDING_RTT, hba->capabilities) + 1;
/* Read crypto capabilities */
err = ufshcd_hba_init_crypto_capabilities(hba);
@@ -6415,8 +6414,8 @@ static bool ufshcd_wb_curr_buff_threshold_check(struct ufs_hba *hba,
}
if (!cur_buf) {
- dev_info(hba->dev, "dCurWBBuf: %d WB disabled until free-space is available\n",
- cur_buf);
+ dev_warn_once(hba->dev, "dCurWBBuf: %d WB disabled until free-space is available\n",
+ cur_buf);
return false;
}
/* Let it continue to flush when available buffer exceeds threshold */
@@ -8611,8 +8610,6 @@ static void ufshcd_set_rtt(struct ufs_hba *hba)
struct ufs_dev_info *dev_info = &hba->dev_info;
u32 rtt = 0;
u32 dev_rtt = 0;
- int host_rtt_cap = hba->vops && hba->vops->max_num_rtt ?
- hba->vops->max_num_rtt : hba->nortt;
/* RTT override makes sense only for UFS-4.0 and above */
if (dev_info->wspecversion < 0x400)
@@ -8628,7 +8625,7 @@ static void ufshcd_set_rtt(struct ufs_hba *hba)
if (dev_rtt != DEFAULT_MAX_NUM_RTT)
return;
- rtt = min_t(int, dev_info->rtt_cap, host_rtt_cap);
+ rtt = min_t(int, dev_info->rtt_cap, hba->nortt);
if (rtt == dev_rtt)
return;
diff --git a/drivers/ufs/host/ufs-mediatek.c b/drivers/ufs/host/ufs-mediatek.c
index 3991a51263a6..58701ca95edd 100644
--- a/drivers/ufs/host/ufs-mediatek.c
+++ b/drivers/ufs/host/ufs-mediatek.c
@@ -2183,6 +2183,16 @@ static int ufs_mtk_clk_scale_notify(struct ufs_hba *hba, bool scale_up,
return 0;
}
+static int ufs_mtk_get_hba_nortt(struct ufs_hba *hba)
+{
+ struct ufs_mtk_host *host = ufshcd_get_variant(hba);
+
+ if (host->legacy_ip_ver || host->ip_ver < IP_VER_MT6995_B0)
+ return MTK_MAX_NUM_RTT_LEGACY;
+
+ return FIELD_GET(MASK_NUMBER_OUTSTANDING_RTT, hba->capabilities) + 1;
+}
+
static int ufs_mtk_get_hba_mac(struct ufs_hba *hba)
{
struct ufs_mtk_host *host = ufshcd_get_variant(hba);
@@ -2322,7 +2332,6 @@ static void ufs_mtk_config_scsi_dev(struct scsi_device *sdev)
*/
static const struct ufs_hba_variant_ops ufs_hba_mtk_vops = {
.name = "mediatek.ufshci",
- .max_num_rtt = MTK_MAX_NUM_RTT,
.init = ufs_mtk_init,
.get_ufs_hci_version = ufs_mtk_get_ufs_hci_version,
.setup_clocks = ufs_mtk_setup_clocks,
@@ -2339,6 +2348,7 @@ static const struct ufs_hba_variant_ops ufs_hba_mtk_vops = {
.event_notify = ufs_mtk_event_notify,
.config_scaling_param = ufs_mtk_config_scaling_param,
.clk_scale_notify = ufs_mtk_clk_scale_notify,
+ .get_hba_nortt = ufs_mtk_get_hba_nortt,
/* mcq vops */
.get_hba_mac = ufs_mtk_get_hba_mac,
.op_runtime_config = ufs_mtk_op_runtime_config,
diff --git a/drivers/ufs/host/ufs-mediatek.h b/drivers/ufs/host/ufs-mediatek.h
index 8547a6f04990..73cdc726f290 100644
--- a/drivers/ufs/host/ufs-mediatek.h
+++ b/drivers/ufs/host/ufs-mediatek.h
@@ -203,8 +203,8 @@ struct ufs_mtk_host {
/* MTK delay of autosuspend: 500 ms */
#define MTK_RPM_AUTOSUSPEND_DELAY_MS 500
-/* MTK RTT support number */
-#define MTK_MAX_NUM_RTT 2
+/* MTK RTT support number for platforms before MT6995 B0 */
+#define MTK_MAX_NUM_RTT_LEGACY 2
/* UFSHCI MTK ip version value */
enum {
diff --git a/drivers/ufs/host/ufs-qcom.c b/drivers/ufs/host/ufs-qcom.c
index 291c43448764..0500320545c4 100644
--- a/drivers/ufs/host/ufs-qcom.c
+++ b/drivers/ufs/host/ufs-qcom.c
@@ -2794,7 +2794,7 @@ static int ufs_qcom_get_rx_fom(struct ufs_hba *hba,
if (ret) {
dev_err(hba->dev, "%s: Failed to apply TX EQ settings for HS-G%u: %d\n",
__func__, gear, ret);
- return ret;
+ goto link_recover_and_restore;
}
/* Force PMC to target HS Gear to use new TX Equalization settings. */
@@ -2802,16 +2802,15 @@ static int ufs_qcom_get_rx_fom(struct ufs_hba *hba,
if (ret) {
dev_err(hba->dev, "%s: Failed to change power mode to HS-G%u, Rate-%s: %d\n",
__func__, gear, ufs_hs_rate_to_str(rate), ret);
- return ret;
+ goto link_recover_and_restore;
}
ret = ufs_qcom_host_sw_rx_fom(hba, pwr_mode->lane_rx, fom);
- if (ret) {
+ if (ret)
dev_err(hba->dev, "Failed to get SW FOM of TX (PreShoot: %u, DeEmphasis: %u): %d\n",
d_iter->preshoot, d_iter->deemphasis, ret);
- return ret;
- }
+link_recover_and_restore:
/* Restore Device's TX Equalization settings. */
ret = ufshcd_apply_tx_eq_settings(hba, &hba->tx_eq_params[gear - 1], gear);
if (ret) {
diff --git a/drivers/ufs/host/ufshcd-pltfrm.c b/drivers/ufs/host/ufshcd-pltfrm.c
index c2dafb583cf5..5ac7afe75934 100644
--- a/drivers/ufs/host/ufshcd-pltfrm.c
+++ b/drivers/ufs/host/ufshcd-pltfrm.c
@@ -210,6 +210,158 @@ static void ufshcd_init_lanes_per_dir(struct ufs_hba *hba)
}
}
+static int ufshcd_parse_tx_precode_enable(struct ufs_hba *hba,
+ bool host_precode_en[UFS_MAX_LANES],
+ bool device_precode_en[UFS_MAX_LANES])
+{
+ const char *prop_name = "tx-precode-enable-g6";
+ u32 num_elems = 2 * hba->lanes_per_direction;
+ const u32 lpd = hba->lanes_per_direction;
+ u32 precode[UFS_MAX_LANES * 2];
+ struct device *dev = hba->dev;
+ int count, err, i;
+
+ count = of_property_count_u32_elems(dev->of_node, prop_name);
+ if (count == -EINVAL || count == -ENODATA)
+ return 0;
+
+ if (count < 0)
+ return count;
+
+ if (count != num_elems) {
+ dev_err(dev, "Property %s has invalid count (%d), expecting %u\n",
+ prop_name, count, num_elems);
+ return -EINVAL;
+ }
+
+ err = of_property_read_u32_array(dev->of_node, prop_name, precode, num_elems);
+ if (err) {
+ dev_err(dev, "Failed to read %s property, %d\n", prop_name, err);
+ return err;
+ }
+
+ for (i = 0; i < num_elems; i++) {
+ if (precode[i] > 1) {
+ dev_err(dev, "Invalid TX precode value (%u) in %s property\n",
+ precode[i], prop_name);
+ return -EINVAL;
+ }
+ }
+
+ for (i = 0; i < lpd; i++) {
+ host_precode_en[i] = precode[i * 2];
+ device_precode_en[i] = precode[i * 2 + 1];
+ }
+
+ return 0;
+}
+
+static int ufshcd_parse_tx_eq_value_array(struct ufs_hba *hba,
+ const char *prop_name,
+ const u32 max_value,
+ u32 values[UFS_MAX_LANES * 2])
+{
+ u32 num_elems = 2 * hba->lanes_per_direction;
+ struct device *dev = hba->dev;
+ int count, err, i;
+
+ count = of_property_count_u32_elems(dev->of_node, prop_name);
+ if (count < 0)
+ return count;
+
+ if (count != num_elems) {
+ dev_err(dev, "Property %s has invalid count (%d), expecting %u\n",
+ prop_name, count, num_elems);
+ return -EINVAL;
+ }
+
+ err = of_property_read_u32_array(dev->of_node, prop_name, values, num_elems);
+ if (err) {
+ dev_err(dev, "Failed to read %s property, %d\n", prop_name, err);
+ return err;
+ }
+
+ for (i = 0; i < num_elems; i++) {
+ if (values[i] >= max_value) {
+ dev_err(dev, "Invalid TX EQ value (%u) in %s property\n",
+ values[i], prop_name);
+ return -EINVAL;
+ }
+ }
+
+ return 0;
+}
+
+/**
+ * ufshcd_parse_tx_eq_settings_for_gear - Parse static TX EQ DT settings for one gear
+ * @hba: per adapter instance
+ * @gear: target HS gear
+ *
+ * Reads the txeq-preshoot-gN, txeq-deemphasis-gN, and (for G6)
+ * tx-precode-enable-g6 device-tree properties.
+ * If all present values are valid, stores them as static TX Equalization
+ * settings for the given gear.
+ */
+static void ufshcd_parse_tx_eq_settings_for_gear(struct ufs_hba *hba, int gear)
+{
+ bool device_precode_en[UFS_MAX_LANES] = { false };
+ bool host_precode_en[UFS_MAX_LANES] = { false };
+ const u32 lpd = hba->lanes_per_direction;
+ struct ufshcd_tx_eq_params *params;
+ u32 deemphasis[UFS_MAX_LANES * 2];
+ u32 preshoot[UFS_MAX_LANES * 2];
+ char prop_name[MAX_PROP_SIZE];
+ int err, lane;
+
+ snprintf(prop_name, MAX_PROP_SIZE, "txeq-preshoot-g%d", gear);
+ err = ufshcd_parse_tx_eq_value_array(hba, prop_name, TX_HS_NUM_PRESHOOT, preshoot);
+ if (err)
+ return;
+
+ snprintf(prop_name, MAX_PROP_SIZE, "txeq-deemphasis-g%d", gear);
+ err = ufshcd_parse_tx_eq_value_array(hba, prop_name, TX_HS_NUM_DEEMPHASIS, deemphasis);
+ if (err)
+ return;
+
+ if (gear == UFS_HS_G6) {
+ err = ufshcd_parse_tx_precode_enable(hba, host_precode_en, device_precode_en);
+ if (err)
+ return;
+ }
+
+ params = &hba->tx_eq_params[gear - 1];
+ for (lane = 0; lane < lpd; lane++) {
+ params->host[lane].preshoot = preshoot[lane * 2];
+ params->host[lane].deemphasis = deemphasis[lane * 2];
+ params->host[lane].precode_en = host_precode_en[lane];
+
+ params->device[lane].preshoot = preshoot[lane * 2 + 1];
+ params->device[lane].deemphasis = deemphasis[lane * 2 + 1];
+ params->device[lane].precode_en = device_precode_en[lane];
+ }
+
+ params->is_valid = true;
+ params->from_dt = true;
+}
+
+static void ufshcd_parse_static_tx_eq_settings(struct ufs_hba *hba)
+{
+ const u32 lpd = hba->lanes_per_direction;
+ int gear;
+
+ if (!lpd)
+ return;
+
+ if (lpd > UFS_MAX_LANES) {
+ dev_warn(hba->dev, "lanes_per_direction (%u) exceeds UFS_MAX_LANES (%u)\n",
+ lpd, UFS_MAX_LANES);
+ return;
+ }
+
+ for (gear = UFS_HS_G1; gear <= UFS_HS_GEAR_MAX; gear++)
+ ufshcd_parse_tx_eq_settings_for_gear(hba, gear);
+}
+
/**
* ufshcd_parse_clock_min_max_freq - Parse MIN and MAX clocks freq
* @hba: per adapter instance
@@ -528,6 +680,8 @@ int ufshcd_pltfrm_init(struct platform_device *pdev,
ufshcd_init_lanes_per_dir(hba);
+ ufshcd_parse_static_tx_eq_settings(hba);
+
err = ufshcd_parse_operating_points(hba);
if (err) {
dev_err(dev, "%s: OPP parse failed %d\n", __func__, err);