mirror of
https://github.com/linux-msm/laptops-kernel.git
synced 2026-08-13 14:19:53 -07:00
Merge tag 'mmc-v6.18' of git://git.kernel.org/pub/scm/linux/kernel/git/ulfh/mmc
Pull MMC updates from Ulf Hansson:
"MMC core:
- Improve RPMB frame handling code
- Add support for a new max-sd-hs-hz DT property to limit frequency
- Add support to manage regulator-under-voltage events
- Support regulator-under-voltage for eMMC to mitigate data
corruptions
- Add mmc_read_tuning() to allow a host to validate its tuning
sequence
- Add some helpers to align checks for CMD23 support
- Read the CCCR register for SDIO over SPI rather than the
unsupported CMD7
MMC host:
- Add COMPILE_TEST option for a couple of drivers
- Convert drivers to use the modern PM macros
- dw_mmc-exynos: Enable support for the Exynos8890 variant
- mmc_spi: Don't use crc ack during multiple block read
- renesas_sdhi: Enable 64-bit polling mode for R-Car gen3 and
RZ/G2L SoCs
- rtsx_usb: Add support for over-current-protection
- sdhci-cadence: Add support for multi-block read gap tuning
- sdhci-msm:
- Add support for tuning for SDR50 mode for SD cards
- Enable support for the Lemans variant
- sdhci-pci: Disable SD card clock before update for a few
Intel platforms
- sdhci-pxav3: Add support for UHS pinctrl settings
- tmio: Add 64-bit read/write support in polling mode
MEMSTICK:
- Convert to use timeouts to prevent indefinite waiting
- rtsx_usb: Add support for over-current-protection"
* tag 'mmc-v6.18' of git://git.kernel.org/pub/scm/linux/kernel/git/ulfh/mmc: (77 commits)
dt-bindings: mmc: samsung,exynos-dw-mshc: add specific compatible for exynos8890
mmc: select REGMAP_MMIO with MMC_LOONGSON2
mmc: add COMPILE_TEST to multiple drivers
mmc: core: Improve RPMB frame handling code
mmc: core: Fix variable shadowing in mmc_route_rpmb_frames()
mmc: core: Parse and use the new max-sd-hs-hz DT property
dt-bindings: mmc: controller: Add max-sd-hs-hz property
mmc: sdhci-msm: Enable tuning for SDR50 mode for SD card
dt-bindings: mmc: sdhci-msm: Document the Lemans compatible
mmc: sh_mmcif: Remove dummy PM resume callback
dt-bindings: mmc: sdhci-pxa: Add minItems to pinctrl-names
mmc: sdio: Drop dev_pm_domain_detach() call
mmc: dw_mmc-rockchip: use modern PM macros
mmc: dw_mmc-pci: use modern PM macros
mmc: dw_mmc-k3: use modern PM macros
mmc: dw_mmc: exynos: use modern PM macros
mmc: via-sdmmc: use modern PM macros
mmc: sdhci-msm: use modern PM macros
mmc: mtk-sd: use modern PM macros
mmc: wmt-sdmmc: use modern PM macros
...
This commit is contained in:
@@ -90,6 +90,7 @@ required:
|
||||
|
||||
allOf:
|
||||
- $ref: sdhci-common.yaml#
|
||||
- $ref: mmc-controller-common.yaml#
|
||||
|
||||
unevaluatedProperties: false
|
||||
|
||||
|
||||
@@ -93,6 +93,14 @@ properties:
|
||||
minimum: 400000
|
||||
maximum: 384000000
|
||||
|
||||
max-sd-hs-hz:
|
||||
description: |
|
||||
Maximum frequency (in Hz) to be used for SD cards operating in
|
||||
High-Speed (HS) mode.
|
||||
minimum: 400000
|
||||
maximum: 50000000
|
||||
default: 50000000
|
||||
|
||||
disable-wp:
|
||||
$ref: /schemas/types.yaml#/definitions/flag
|
||||
description:
|
||||
|
||||
@@ -31,6 +31,7 @@ properties:
|
||||
- samsung,exynos5433-dw-mshc-smu
|
||||
- samsung,exynos7885-dw-mshc-smu
|
||||
- samsung,exynos850-dw-mshc-smu
|
||||
- samsung,exynos8890-dw-mshc-smu
|
||||
- samsung,exynos8895-dw-mshc-smu
|
||||
- const: samsung,exynos7-dw-mshc-smu
|
||||
|
||||
|
||||
@@ -48,6 +48,7 @@ properties:
|
||||
- qcom,qcs615-sdhci
|
||||
- qcom,qcs8300-sdhci
|
||||
- qcom,qdu1000-sdhci
|
||||
- qcom,sa8775p-sdhci
|
||||
- qcom,sar2130p-sdhci
|
||||
- qcom,sc7180-sdhci
|
||||
- qcom,sc7280-sdhci
|
||||
|
||||
@@ -44,12 +44,29 @@ allOf:
|
||||
items:
|
||||
- const: default
|
||||
- const: state_cmd_gpio
|
||||
pinctrl-0:
|
||||
description:
|
||||
Should contain default pinctrl.
|
||||
minItems: 1
|
||||
|
||||
pinctrl-1:
|
||||
description:
|
||||
Should switch CMD pin to GPIO mode as a high output.
|
||||
- if:
|
||||
properties:
|
||||
compatible:
|
||||
contains:
|
||||
const: mrvl,pxav3-mmc
|
||||
then:
|
||||
properties:
|
||||
pinctrl-names:
|
||||
description:
|
||||
Optional for increasing stability of the controller at fast bus clocks.
|
||||
items:
|
||||
- const: default
|
||||
- const: state_uhs
|
||||
minItems: 1
|
||||
|
||||
pinctrl-1:
|
||||
description:
|
||||
Should switch the drive strength of the data pins to high.
|
||||
|
||||
properties:
|
||||
compatible:
|
||||
@@ -82,6 +99,14 @@ properties:
|
||||
- const: io
|
||||
- const: core
|
||||
|
||||
pinctrl-names: true
|
||||
|
||||
pinctrl-0:
|
||||
description:
|
||||
Should contain default pinctrl.
|
||||
|
||||
pinctrl-1: true
|
||||
|
||||
mrvl,clk-delay-cycles:
|
||||
description: Specify a number of cycles to delay for tuning.
|
||||
$ref: /schemas/types.yaml#/definitions/uint32
|
||||
|
||||
@@ -370,7 +370,9 @@ int memstick_set_rw_addr(struct memstick_dev *card)
|
||||
{
|
||||
card->next_request = h_memstick_set_rw_addr;
|
||||
memstick_new_req(card->host);
|
||||
wait_for_completion(&card->mrq_complete);
|
||||
if (!wait_for_completion_timeout(&card->mrq_complete,
|
||||
msecs_to_jiffies(500)))
|
||||
card->current_mrq.error = -ETIMEDOUT;
|
||||
|
||||
return card->current_mrq.error;
|
||||
}
|
||||
@@ -404,7 +406,9 @@ static struct memstick_dev *memstick_alloc_card(struct memstick_host *host)
|
||||
|
||||
card->next_request = h_memstick_read_dev_id;
|
||||
memstick_new_req(host);
|
||||
wait_for_completion(&card->mrq_complete);
|
||||
if (!wait_for_completion_timeout(&card->mrq_complete,
|
||||
msecs_to_jiffies(500)))
|
||||
card->current_mrq.error = -ETIMEDOUT;
|
||||
|
||||
if (card->current_mrq.error)
|
||||
goto err_out;
|
||||
|
||||
@@ -216,7 +216,10 @@ static int ms_power_off(struct rtsx_usb_ms *host)
|
||||
|
||||
rtsx_usb_add_cmd(ucr, WRITE_REG_CMD, CARD_CLK_EN, MS_CLK_EN, 0);
|
||||
rtsx_usb_add_cmd(ucr, WRITE_REG_CMD, CARD_OE, MS_OUTPUT_EN, 0);
|
||||
|
||||
rtsx_usb_add_cmd(ucr, WRITE_REG_CMD, CARD_PWR_CTL,
|
||||
POWER_MASK, POWER_OFF);
|
||||
rtsx_usb_add_cmd(ucr, WRITE_REG_CMD, CARD_PWR_CTL,
|
||||
POWER_MASK | LDO3318_PWR_MASK, POWER_OFF | LDO_SUSPEND);
|
||||
err = rtsx_usb_send_cmd(ucr, MODE_C, 100);
|
||||
if (err < 0)
|
||||
return err;
|
||||
|
||||
@@ -552,6 +552,10 @@ static int rtsx_usb_reset_chip(struct rtsx_ucr *ucr)
|
||||
ret = rtsx_usb_send_cmd(ucr, MODE_C, 100);
|
||||
if (ret)
|
||||
return ret;
|
||||
/* config OCP */
|
||||
rtsx_usb_write_register(ucr, OCPCTL, MS_OCP_DETECT_EN, MS_OCP_DETECT_EN);
|
||||
rtsx_usb_write_register(ucr, OCPPARA1, 0xF0, 0x50);
|
||||
rtsx_usb_write_register(ucr, OCPPARA2, 0x7, 0x3);
|
||||
|
||||
/* config non-crystal mode */
|
||||
rtsx_usb_read_register(ucr, CFG_MODE, &val);
|
||||
@@ -722,6 +726,9 @@ static int rtsx_usb_suspend(struct usb_interface *intf, pm_message_t message)
|
||||
if (val & (SD_CD | MS_CD)) {
|
||||
device_for_each_child(&intf->dev, NULL, rtsx_usb_resume_child);
|
||||
return -EAGAIN;
|
||||
} else {
|
||||
/* if the card does not exists, clear OCP status */
|
||||
rtsx_usb_write_register(ucr, OCPCTL, MS_OCP_CLEAR, MS_OCP_CLEAR);
|
||||
}
|
||||
} else {
|
||||
/* There is an ongoing operation*/
|
||||
|
||||
+19
-27
@@ -121,6 +121,10 @@ struct rpmb_frame {
|
||||
#define RPMB_READ_DATA 0x4 /* Read data from RPMB partition */
|
||||
#define RPMB_RESULT_READ 0x5 /* Read result request (Internal) */
|
||||
|
||||
#define RPMB_FRAME_SIZE sizeof(struct rpmb_frame)
|
||||
#define CHECK_SIZE_NEQ(val) ((val) != sizeof(struct rpmb_frame))
|
||||
#define CHECK_SIZE_ALIGNED(val) IS_ALIGNED((val), sizeof(struct rpmb_frame))
|
||||
|
||||
static DEFINE_MUTEX(block_mutex);
|
||||
|
||||
/*
|
||||
@@ -1768,8 +1772,7 @@ static void mmc_blk_rw_rq_prep(struct mmc_queue_req *mqrq,
|
||||
* these, while retaining features like reliable writes.
|
||||
*/
|
||||
if ((md->flags & MMC_BLK_CMD23) && mmc_op_multi(brq->cmd.opcode) &&
|
||||
(do_rel_wr || !(card->quirks & MMC_QUIRK_BLK_NO_CMD23) ||
|
||||
do_data_tag)) {
|
||||
(do_rel_wr || !mmc_card_blk_no_cmd23(card) || do_data_tag)) {
|
||||
brq->sbc.opcode = MMC_SET_BLOCK_COUNT;
|
||||
brq->sbc.arg = brq->data.blocks |
|
||||
(do_rel_wr ? (1 << 31) : 0) |
|
||||
@@ -2618,13 +2621,8 @@ static struct mmc_blk_data *mmc_blk_alloc_req(struct mmc_card *card,
|
||||
*/
|
||||
md->read_only = mmc_blk_readonly(card);
|
||||
|
||||
if (mmc_host_can_cmd23(card->host)) {
|
||||
if ((mmc_card_mmc(card) &&
|
||||
card->csd.mmca_vsn >= CSD_SPEC_VER_3) ||
|
||||
(mmc_card_sd(card) && !mmc_card_ult_capacity(card) &&
|
||||
card->scr.cmds & SD_SCR_CMD23_SUPPORT))
|
||||
md->flags |= MMC_BLK_CMD23;
|
||||
}
|
||||
if (mmc_host_can_cmd23(card->host) && mmc_card_can_cmd23(card))
|
||||
md->flags |= MMC_BLK_CMD23;
|
||||
|
||||
if (md->flags & MMC_BLK_CMD23 &&
|
||||
((card->ext_csd.rel_param & EXT_CSD_WR_REL_PARAM_EN) ||
|
||||
@@ -2864,12 +2862,12 @@ static void set_idata(struct mmc_blk_ioc_data *idata, u32 opcode,
|
||||
* The size of an RPMB frame must match what's expected by the
|
||||
* hardware.
|
||||
*/
|
||||
BUILD_BUG_ON(sizeof(struct rpmb_frame) != 512);
|
||||
static_assert(!CHECK_SIZE_NEQ(512), "RPMB frame size must be 512 bytes");
|
||||
|
||||
idata->ic.opcode = opcode;
|
||||
idata->ic.flags = MMC_RSP_R1 | MMC_CMD_ADTC;
|
||||
idata->ic.write_flag = write_flag;
|
||||
idata->ic.blksz = sizeof(struct rpmb_frame);
|
||||
idata->ic.blksz = RPMB_FRAME_SIZE;
|
||||
idata->ic.blocks = buf_bytes / idata->ic.blksz;
|
||||
idata->buf = buf;
|
||||
idata->buf_bytes = buf_bytes;
|
||||
@@ -2893,32 +2891,28 @@ static int mmc_route_rpmb_frames(struct device *dev, u8 *req,
|
||||
if (IS_ERR(md->queue.card))
|
||||
return PTR_ERR(md->queue.card);
|
||||
|
||||
if (req_len < sizeof(*frm))
|
||||
if (req_len < RPMB_FRAME_SIZE)
|
||||
return -EINVAL;
|
||||
|
||||
req_type = be16_to_cpu(frm->req_resp);
|
||||
switch (req_type) {
|
||||
case RPMB_PROGRAM_KEY:
|
||||
if (req_len != sizeof(struct rpmb_frame) ||
|
||||
resp_len != sizeof(struct rpmb_frame))
|
||||
if (CHECK_SIZE_NEQ(req_len) || CHECK_SIZE_NEQ(resp_len))
|
||||
return -EINVAL;
|
||||
write = true;
|
||||
break;
|
||||
case RPMB_GET_WRITE_COUNTER:
|
||||
if (req_len != sizeof(struct rpmb_frame) ||
|
||||
resp_len != sizeof(struct rpmb_frame))
|
||||
if (CHECK_SIZE_NEQ(req_len) || CHECK_SIZE_NEQ(resp_len))
|
||||
return -EINVAL;
|
||||
write = false;
|
||||
break;
|
||||
case RPMB_WRITE_DATA:
|
||||
if (req_len % sizeof(struct rpmb_frame) ||
|
||||
resp_len != sizeof(struct rpmb_frame))
|
||||
if (!CHECK_SIZE_ALIGNED(req_len) || CHECK_SIZE_NEQ(resp_len))
|
||||
return -EINVAL;
|
||||
write = true;
|
||||
break;
|
||||
case RPMB_READ_DATA:
|
||||
if (req_len != sizeof(struct rpmb_frame) ||
|
||||
resp_len % sizeof(struct rpmb_frame))
|
||||
if (CHECK_SIZE_NEQ(req_len) || !CHECK_SIZE_ALIGNED(resp_len))
|
||||
return -EINVAL;
|
||||
write = false;
|
||||
break;
|
||||
@@ -2926,25 +2920,23 @@ static int mmc_route_rpmb_frames(struct device *dev, u8 *req,
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
if (write)
|
||||
cmd_count = 3;
|
||||
else
|
||||
cmd_count = 2;
|
||||
/* Write operations require 3 commands, read operations require 2 */
|
||||
cmd_count = write ? 3 : 2;
|
||||
|
||||
idata = alloc_idata(rpmb, cmd_count);
|
||||
if (!idata)
|
||||
return -ENOMEM;
|
||||
|
||||
if (write) {
|
||||
struct rpmb_frame *frm = (struct rpmb_frame *)resp;
|
||||
struct rpmb_frame *resp_frm = (struct rpmb_frame *)resp;
|
||||
|
||||
/* Send write request frame(s) */
|
||||
set_idata(idata[0], MMC_WRITE_MULTIPLE_BLOCK,
|
||||
1 | MMC_CMD23_ARG_REL_WR, req, req_len);
|
||||
|
||||
/* Send result request frame */
|
||||
memset(frm, 0, sizeof(*frm));
|
||||
frm->req_resp = cpu_to_be16(RPMB_RESULT_READ);
|
||||
memset(resp_frm, 0, RPMB_FRAME_SIZE);
|
||||
resp_frm->req_resp = cpu_to_be16(RPMB_RESULT_READ);
|
||||
set_idata(idata[1], MMC_WRITE_MULTIPLE_BLOCK, 1, resp,
|
||||
resp_len);
|
||||
|
||||
|
||||
@@ -19,6 +19,7 @@
|
||||
|
||||
#include <linux/mmc/card.h>
|
||||
#include <linux/mmc/host.h>
|
||||
#include <linux/mmc/mmc.h>
|
||||
|
||||
#include "core.h"
|
||||
#include "card.h"
|
||||
@@ -383,6 +384,14 @@ int mmc_add_card(struct mmc_card *card)
|
||||
|
||||
mmc_card_set_present(card);
|
||||
|
||||
/*
|
||||
* Register for undervoltage notification if the card supports
|
||||
* power-off notification, enabling emergency shutdowns.
|
||||
*/
|
||||
if (mmc_card_mmc(card) &&
|
||||
card->ext_csd.power_off_notification == EXT_CSD_POWER_ON)
|
||||
mmc_regulator_register_undervoltage_notifier(card->host);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -394,6 +403,9 @@ void mmc_remove_card(struct mmc_card *card)
|
||||
{
|
||||
struct mmc_host *host = card->host;
|
||||
|
||||
if (mmc_card_present(card))
|
||||
mmc_regulator_unregister_undervoltage_notifier(host);
|
||||
|
||||
mmc_remove_card_debugfs(card);
|
||||
|
||||
if (mmc_card_present(card)) {
|
||||
|
||||
@@ -245,14 +245,19 @@ static inline int mmc_blksz_for_byte_mode(const struct mmc_card *c)
|
||||
return c->quirks & MMC_QUIRK_BLKSZ_FOR_BYTE_MODE;
|
||||
}
|
||||
|
||||
static inline int mmc_card_nonstd_func_interface(const struct mmc_card *c)
|
||||
{
|
||||
return c->quirks & MMC_QUIRK_NONSTD_FUNC_IF;
|
||||
}
|
||||
|
||||
static inline int mmc_card_disable_cd(const struct mmc_card *c)
|
||||
{
|
||||
return c->quirks & MMC_QUIRK_DISABLE_CD;
|
||||
}
|
||||
|
||||
static inline int mmc_card_nonstd_func_interface(const struct mmc_card *c)
|
||||
static inline int mmc_card_blk_no_cmd23(const struct mmc_card *c)
|
||||
{
|
||||
return c->quirks & MMC_QUIRK_NONSTD_FUNC_IF;
|
||||
return c->quirks & MMC_QUIRK_BLK_NO_CMD23;
|
||||
}
|
||||
|
||||
static inline int mmc_card_broken_byte_mode_512(const struct mmc_card *c)
|
||||
|
||||
@@ -1398,6 +1398,29 @@ void mmc_power_cycle(struct mmc_host *host, u32 ocr)
|
||||
mmc_power_up(host, ocr);
|
||||
}
|
||||
|
||||
/**
|
||||
* mmc_handle_undervoltage - Handle an undervoltage event on the MMC bus
|
||||
* @host: The MMC host that detected the undervoltage condition
|
||||
*
|
||||
* This function is called when an undervoltage event is detected on one of
|
||||
* the MMC regulators.
|
||||
*
|
||||
* Returns: 0 on success or a negative error code on failure.
|
||||
*/
|
||||
int mmc_handle_undervoltage(struct mmc_host *host)
|
||||
{
|
||||
/* Stop the host to prevent races with card removal */
|
||||
__mmc_stop_host(host);
|
||||
|
||||
if (!host->bus_ops || !host->bus_ops->handle_undervoltage)
|
||||
return 0;
|
||||
|
||||
dev_warn(mmc_dev(host), "%s: Undervoltage detected, initiating emergency stop\n",
|
||||
mmc_hostname(host));
|
||||
|
||||
return host->bus_ops->handle_undervoltage(host);
|
||||
}
|
||||
|
||||
/*
|
||||
* Assign a mmc bus handler to a host. Only one bus handler may control a
|
||||
* host at any given time.
|
||||
@@ -1875,6 +1898,15 @@ bool mmc_card_can_secure_erase_trim(struct mmc_card *card)
|
||||
}
|
||||
EXPORT_SYMBOL(mmc_card_can_secure_erase_trim);
|
||||
|
||||
bool mmc_card_can_cmd23(struct mmc_card *card)
|
||||
{
|
||||
return ((mmc_card_mmc(card) &&
|
||||
card->csd.mmca_vsn >= CSD_SPEC_VER_3) ||
|
||||
(mmc_card_sd(card) && !mmc_card_ult_capacity(card) &&
|
||||
card->scr.cmds & SD_SCR_CMD23_SUPPORT));
|
||||
}
|
||||
EXPORT_SYMBOL(mmc_card_can_cmd23);
|
||||
|
||||
int mmc_erase_group_aligned(struct mmc_card *card, sector_t from,
|
||||
unsigned int nr)
|
||||
{
|
||||
|
||||
@@ -31,6 +31,7 @@ struct mmc_bus_ops {
|
||||
int (*sw_reset)(struct mmc_host *);
|
||||
bool (*cache_enabled)(struct mmc_host *);
|
||||
int (*flush_cache)(struct mmc_host *);
|
||||
int (*handle_undervoltage)(struct mmc_host *host);
|
||||
};
|
||||
|
||||
void mmc_attach_bus(struct mmc_host *host, const struct mmc_bus_ops *ops);
|
||||
@@ -59,6 +60,10 @@ void mmc_power_off(struct mmc_host *host);
|
||||
void mmc_power_cycle(struct mmc_host *host, u32 ocr);
|
||||
void mmc_set_initial_state(struct mmc_host *host);
|
||||
u32 mmc_vddrange_to_ocrmask(int vdd_min, int vdd_max);
|
||||
int mmc_handle_undervoltage(struct mmc_host *host);
|
||||
void mmc_regulator_register_undervoltage_notifier(struct mmc_host *host);
|
||||
void mmc_regulator_unregister_undervoltage_notifier(struct mmc_host *host);
|
||||
void mmc_undervoltage_workfn(struct work_struct *work);
|
||||
|
||||
static inline void mmc_delay(unsigned int ms)
|
||||
{
|
||||
@@ -123,6 +128,7 @@ bool mmc_card_can_trim(struct mmc_card *card);
|
||||
bool mmc_card_can_discard(struct mmc_card *card);
|
||||
bool mmc_card_can_sanitize(struct mmc_card *card);
|
||||
bool mmc_card_can_secure_erase_trim(struct mmc_card *card);
|
||||
bool mmc_card_can_cmd23(struct mmc_card *card);
|
||||
int mmc_erase_group_aligned(struct mmc_card *card, sector_t from, unsigned int nr);
|
||||
unsigned int mmc_calc_max_discard(struct mmc_card *card);
|
||||
|
||||
|
||||
@@ -302,6 +302,8 @@ int mmc_of_parse(struct mmc_host *host)
|
||||
/* f_max is obtained from the optional "max-frequency" property */
|
||||
device_property_read_u32(dev, "max-frequency", &host->f_max);
|
||||
|
||||
device_property_read_u32(dev, "max-sd-hs-hz", &host->max_sd_hs_hz);
|
||||
|
||||
/*
|
||||
* Configure CD and WP pins. They are both by default active low to
|
||||
* match the SDHCI spec. If GPIOs are provided for CD and / or WP, the
|
||||
@@ -564,6 +566,8 @@ struct mmc_host *mmc_alloc_host(int extra, struct device *dev)
|
||||
INIT_WORK(&host->sdio_irq_work, sdio_irq_work);
|
||||
timer_setup(&host->retune_timer, mmc_retune_timer, 0);
|
||||
|
||||
INIT_WORK(&host->supply.uv_work, mmc_undervoltage_workfn);
|
||||
|
||||
/*
|
||||
* By default, hosts do not support SGIO or large requests.
|
||||
* They have to set these according to their abilities.
|
||||
|
||||
+67
-3
@@ -36,6 +36,7 @@
|
||||
enum mmc_poweroff_type {
|
||||
MMC_POWEROFF_SUSPEND,
|
||||
MMC_POWEROFF_SHUTDOWN,
|
||||
MMC_POWEROFF_UNDERVOLTAGE,
|
||||
MMC_POWEROFF_UNBIND,
|
||||
};
|
||||
|
||||
@@ -2132,9 +2133,15 @@ static int _mmc_suspend(struct mmc_host *host, enum mmc_poweroff_type pm_type)
|
||||
if (mmc_card_suspended(host->card))
|
||||
goto out;
|
||||
|
||||
err = _mmc_flush_cache(host);
|
||||
if (err)
|
||||
goto out;
|
||||
/*
|
||||
* For the undervoltage case, we care more about device integrity.
|
||||
* Avoid cache flush and notify the device to power off quickly.
|
||||
*/
|
||||
if (pm_type != MMC_POWEROFF_UNDERVOLTAGE) {
|
||||
err = _mmc_flush_cache(host);
|
||||
if (err)
|
||||
goto out;
|
||||
}
|
||||
|
||||
if (mmc_card_can_poweroff_notify(host->card) &&
|
||||
mmc_host_can_poweroff_notify(host, pm_type))
|
||||
@@ -2212,6 +2219,13 @@ static int mmc_shutdown(struct mmc_host *host)
|
||||
{
|
||||
int err = 0;
|
||||
|
||||
/*
|
||||
* In case of undervoltage, the card will be powered off (removed) by
|
||||
* _mmc_handle_undervoltage()
|
||||
*/
|
||||
if (mmc_card_removed(host->card))
|
||||
return 0;
|
||||
|
||||
/*
|
||||
* If the card remains suspended at this point and it was done by using
|
||||
* the sleep-cmd (CMD5), we may need to re-initialize it first, to allow
|
||||
@@ -2302,6 +2316,55 @@ static int _mmc_hw_reset(struct mmc_host *host)
|
||||
return mmc_init_card(host, card->ocr, card);
|
||||
}
|
||||
|
||||
/**
|
||||
* _mmc_handle_undervoltage - Handle an undervoltage event for MMC/eMMC devices
|
||||
* @host: MMC host structure
|
||||
*
|
||||
* This function is triggered when an undervoltage condition is detected.
|
||||
* It attempts to transition the device into a low-power or safe state to
|
||||
* prevent data corruption.
|
||||
*
|
||||
* Steps performed:
|
||||
* - Perform an emergency suspend using EXT_CSD_POWER_OFF_SHORT if possible.
|
||||
* - If power-off notify is not supported, fallback mechanisms like sleep or
|
||||
* deselecting the card are attempted.
|
||||
* - Cache flushing is skipped to reduce execution time.
|
||||
* - Mark the card as removed to prevent further interactions after
|
||||
* undervoltage.
|
||||
*
|
||||
* Note: This function does not handle host claiming or releasing. The caller
|
||||
* must ensure that the host is properly claimed before calling this
|
||||
* function and released afterward.
|
||||
*
|
||||
* Returns: 0 on success, or a negative error code if any step fails.
|
||||
*/
|
||||
static int _mmc_handle_undervoltage(struct mmc_host *host)
|
||||
{
|
||||
struct mmc_card *card = host->card;
|
||||
int err;
|
||||
|
||||
/*
|
||||
* Perform an emergency suspend to power off the eMMC quickly.
|
||||
* This ensures the device enters a safe state before power is lost.
|
||||
* We first attempt EXT_CSD_POWER_OFF_SHORT, but if power-off notify
|
||||
* is not supported, we fall back to sleep mode or deselecting the card.
|
||||
* Cache flushing is skipped to minimize delay.
|
||||
*/
|
||||
err = _mmc_suspend(host, MMC_POWEROFF_UNDERVOLTAGE);
|
||||
if (err)
|
||||
pr_err("%s: undervoltage suspend failed: %pe\n",
|
||||
mmc_hostname(host), ERR_PTR(err));
|
||||
|
||||
/*
|
||||
* Mark the card as removed to prevent further operations.
|
||||
* This ensures the system does not attempt to access the device
|
||||
* after an undervoltage event, avoiding potential corruption.
|
||||
*/
|
||||
mmc_card_set_removed(card);
|
||||
|
||||
return err;
|
||||
}
|
||||
|
||||
static const struct mmc_bus_ops mmc_ops = {
|
||||
.remove = mmc_remove,
|
||||
.detect = mmc_detect,
|
||||
@@ -2314,6 +2377,7 @@ static const struct mmc_bus_ops mmc_ops = {
|
||||
.hw_reset = _mmc_hw_reset,
|
||||
.cache_enabled = _mmc_cache_enabled,
|
||||
.flush_cache = _mmc_flush_cache,
|
||||
.handle_undervoltage = _mmc_handle_undervoltage,
|
||||
};
|
||||
|
||||
/*
|
||||
|
||||
@@ -1077,3 +1077,75 @@ int mmc_sanitize(struct mmc_card *card, unsigned int timeout_ms)
|
||||
return err;
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(mmc_sanitize);
|
||||
|
||||
/**
|
||||
* mmc_read_tuning() - read data blocks from the mmc
|
||||
* @host: mmc host doing the read
|
||||
* @blksz: data block size
|
||||
* @blocks: number of blocks to read
|
||||
*
|
||||
* Read one or more blocks of data from the beginning of the mmc. This is a
|
||||
* low-level helper for tuning operation. It is assumed that CMD23 can be used
|
||||
* for multi-block read if the host supports it.
|
||||
*
|
||||
* Note: Allocate and free a temporary buffer to store the data read. The data
|
||||
* is not available outside of the function, only the status of the read
|
||||
* operation.
|
||||
*
|
||||
* Return: 0 in case of success, otherwise -EIO / -ENOMEM / -E2BIG
|
||||
*/
|
||||
int mmc_read_tuning(struct mmc_host *host, unsigned int blksz, unsigned int blocks)
|
||||
{
|
||||
struct mmc_request mrq = {};
|
||||
struct mmc_command sbc = {};
|
||||
struct mmc_command cmd = {};
|
||||
struct mmc_command stop = {};
|
||||
struct mmc_data data = {};
|
||||
struct scatterlist sg;
|
||||
void *buf;
|
||||
unsigned int len;
|
||||
|
||||
if (blocks > 1) {
|
||||
if (mmc_host_can_cmd23(host)) {
|
||||
mrq.sbc = &sbc;
|
||||
sbc.opcode = MMC_SET_BLOCK_COUNT;
|
||||
sbc.arg = blocks;
|
||||
sbc.flags = MMC_RSP_R1 | MMC_CMD_AC;
|
||||
}
|
||||
cmd.opcode = MMC_READ_MULTIPLE_BLOCK;
|
||||
mrq.stop = &stop;
|
||||
stop.opcode = MMC_STOP_TRANSMISSION;
|
||||
stop.flags = MMC_RSP_SPI_R1 | MMC_RSP_R1 | MMC_CMD_AC;
|
||||
} else {
|
||||
cmd.opcode = MMC_READ_SINGLE_BLOCK;
|
||||
}
|
||||
|
||||
mrq.cmd = &cmd;
|
||||
cmd.flags = MMC_RSP_SPI_R1 | MMC_RSP_R1 | MMC_CMD_ADTC;
|
||||
|
||||
mrq.data = &data;
|
||||
data.flags = MMC_DATA_READ;
|
||||
data.blksz = blksz;
|
||||
data.blocks = blocks;
|
||||
data.blk_addr = 0;
|
||||
data.sg = &sg;
|
||||
data.sg_len = 1;
|
||||
data.timeout_ns = 1000000000;
|
||||
|
||||
if (check_mul_overflow(blksz, blocks, &len))
|
||||
return -E2BIG;
|
||||
buf = kmalloc(len, GFP_KERNEL);
|
||||
if (!buf)
|
||||
return -ENOMEM;
|
||||
|
||||
sg_init_one(&sg, buf, len);
|
||||
|
||||
mmc_wait_for_req(host, &mrq);
|
||||
kfree(buf);
|
||||
|
||||
if (sbc.error || cmd.error || data.error)
|
||||
return -EIO;
|
||||
|
||||
return 0;
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(mmc_read_tuning);
|
||||
|
||||
@@ -180,20 +180,14 @@ static int mmc_test_set_blksize(struct mmc_test_card *test, unsigned size)
|
||||
return mmc_set_blocklen(test->card, size);
|
||||
}
|
||||
|
||||
static bool mmc_test_card_cmd23(struct mmc_card *card)
|
||||
{
|
||||
return mmc_card_mmc(card) ||
|
||||
(mmc_card_sd(card) && card->scr.cmds & SD_SCR_CMD23_SUPPORT);
|
||||
}
|
||||
|
||||
static void mmc_test_prepare_sbc(struct mmc_test_card *test,
|
||||
struct mmc_request *mrq, unsigned int blocks)
|
||||
{
|
||||
struct mmc_card *card = test->card;
|
||||
|
||||
if (!mrq->sbc || !mmc_host_can_cmd23(card->host) ||
|
||||
!mmc_test_card_cmd23(card) || !mmc_op_multi(mrq->cmd->opcode) ||
|
||||
(card->quirks & MMC_QUIRK_BLK_NO_CMD23)) {
|
||||
!mmc_card_can_cmd23(card) || !mmc_op_multi(mrq->cmd->opcode) ||
|
||||
mmc_card_blk_no_cmd23(card)) {
|
||||
mrq->sbc = NULL;
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
#include <linux/err.h>
|
||||
#include <linux/log2.h>
|
||||
#include <linux/regulator/consumer.h>
|
||||
#include <linux/workqueue.h>
|
||||
|
||||
#include <linux/mmc/host.h>
|
||||
|
||||
@@ -262,6 +263,82 @@ static inline int mmc_regulator_get_ocrmask(struct regulator *supply)
|
||||
|
||||
#endif /* CONFIG_REGULATOR */
|
||||
|
||||
/* To be called from a high-priority workqueue */
|
||||
void mmc_undervoltage_workfn(struct work_struct *work)
|
||||
{
|
||||
struct mmc_supply *supply;
|
||||
struct mmc_host *host;
|
||||
|
||||
supply = container_of(work, struct mmc_supply, uv_work);
|
||||
host = container_of(supply, struct mmc_host, supply);
|
||||
|
||||
mmc_handle_undervoltage(host);
|
||||
}
|
||||
|
||||
static int mmc_handle_regulator_event(struct notifier_block *nb,
|
||||
unsigned long event, void *data)
|
||||
{
|
||||
struct mmc_supply *supply = container_of(nb, struct mmc_supply,
|
||||
vmmc_nb);
|
||||
struct mmc_host *host = container_of(supply, struct mmc_host, supply);
|
||||
unsigned long flags;
|
||||
|
||||
switch (event) {
|
||||
case REGULATOR_EVENT_UNDER_VOLTAGE:
|
||||
spin_lock_irqsave(&host->lock, flags);
|
||||
if (host->undervoltage) {
|
||||
spin_unlock_irqrestore(&host->lock, flags);
|
||||
return NOTIFY_OK;
|
||||
}
|
||||
|
||||
host->undervoltage = true;
|
||||
spin_unlock_irqrestore(&host->lock, flags);
|
||||
|
||||
queue_work(system_highpri_wq, &host->supply.uv_work);
|
||||
break;
|
||||
default:
|
||||
return NOTIFY_DONE;
|
||||
}
|
||||
|
||||
return NOTIFY_OK;
|
||||
}
|
||||
|
||||
/**
|
||||
* mmc_regulator_register_undervoltage_notifier - Register for undervoltage
|
||||
* events
|
||||
* @host: MMC host
|
||||
*
|
||||
* To be called by a bus driver when a card supporting graceful shutdown
|
||||
* is attached.
|
||||
*/
|
||||
void mmc_regulator_register_undervoltage_notifier(struct mmc_host *host)
|
||||
{
|
||||
int ret;
|
||||
|
||||
if (IS_ERR_OR_NULL(host->supply.vmmc))
|
||||
return;
|
||||
|
||||
host->supply.vmmc_nb.notifier_call = mmc_handle_regulator_event;
|
||||
ret = regulator_register_notifier(host->supply.vmmc,
|
||||
&host->supply.vmmc_nb);
|
||||
if (ret)
|
||||
dev_warn(mmc_dev(host), "Failed to register vmmc notifier: %d\n", ret);
|
||||
}
|
||||
|
||||
/**
|
||||
* mmc_regulator_unregister_undervoltage_notifier - Unregister undervoltage
|
||||
* notifier
|
||||
* @host: MMC host
|
||||
*/
|
||||
void mmc_regulator_unregister_undervoltage_notifier(struct mmc_host *host)
|
||||
{
|
||||
if (IS_ERR_OR_NULL(host->supply.vmmc))
|
||||
return;
|
||||
|
||||
regulator_unregister_notifier(host->supply.vmmc, &host->supply.vmmc_nb);
|
||||
cancel_work_sync(&host->supply.uv_work);
|
||||
}
|
||||
|
||||
/**
|
||||
* mmc_regulator_get_supply - try to get VMMC and VQMMC regulators for a host
|
||||
* @mmc: the host to regulate
|
||||
|
||||
@@ -359,7 +359,7 @@ static int mmc_read_switch(struct mmc_card *card)
|
||||
}
|
||||
|
||||
if (status[13] & SD_MODE_HIGH_SPEED)
|
||||
card->sw_caps.hs_max_dtr = HIGH_SPEED_MAX_DTR;
|
||||
card->sw_caps.hs_max_dtr = card->host->max_sd_hs_hz ?: HIGH_SPEED_MAX_DTR;
|
||||
|
||||
if (card->scr.sda_spec3) {
|
||||
card->sw_caps.sd3_bus_mode = status[13];
|
||||
|
||||
@@ -945,7 +945,11 @@ static void mmc_sdio_remove(struct mmc_host *host)
|
||||
*/
|
||||
static int mmc_sdio_alive(struct mmc_host *host)
|
||||
{
|
||||
return mmc_select_card(host->card);
|
||||
if (!mmc_host_is_spi(host))
|
||||
return mmc_select_card(host->card);
|
||||
else
|
||||
return mmc_io_rw_direct(host->card, 0, 0, SDIO_CCCR_CCCR, 0,
|
||||
NULL);
|
||||
}
|
||||
|
||||
/*
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user