MediaTek soc driver updates for v6.13

This adds support for the MT8188 SoC in the MediaTek Regulator
 Coupler driver, allowing stable GPU DVFS on this chip;
 
 Moreover, this adds a new MediaTek DVFS Resource Collector (DVFSRC)
 driver, allowing to enable other drivers (interconnect, regulator)
 which can now communicate with the DVFSRC hardware.
 
 Last but not least, this includes some cleanups for the CMDQ Helper
 and MediaTek SVS drivers.
 -----BEGIN PGP SIGNATURE-----
 
 iJ4EABYKAEYWIQQn3Xxr56ypAcSHzXSaNgTPrZeEeAUCZyitDigcYW5nZWxvZ2lv
 YWNjaGluby5kZWxyZWdub0Bjb2xsYWJvcmEuY29tAAoJEJo2BM+tl4R4h1wA/1xT
 8kHN16gVzZ6JvjvzOeipCi2YPJ0LCPv4jpvEx4abAP9tFsyVYsQGAE0XS74p3GRT
 r9NhhlV7dnpQmVO/SPClDQ==
 =cgv/
 -----END PGP SIGNATURE-----
gpgsig -----BEGIN PGP SIGNATURE-----
 
 iQIzBAABCgAdFiEEiK/NIGsWEZVxh/FrYKtH/8kJUicFAmcpQSMACgkQYKtH/8kJ
 UieT8A/+LmbcqykZ8LJEvKwIE7zsXEX+Jru2Ani7XMr8hbRTTHS5YsQNaRG1EdqL
 9rDtwhI5L+IK6hn9JDu2RoUF2avECirX2gf+OUiMXy7Cn4BN+lpoir1sImtgSBOz
 zwNq2zLzEU2im/fCaL1uN8gzi8+dR0drX9LYyaghoBlv5RlNu+NE+lx3dIezaEaU
 Plx9dhH2GpbvOp6zVD01Q+6uD3bR8ybvoCPpewWv8Jn3u3+M9qf1krXOHKRqT9Vp
 ErFTF3aC2KBsAPVBHBXgx/uKx918KJIvLRHYh+a5KtstRBjGwpn0SKumNyogNF2I
 czULW9UX7kwqrOR1pVnu+4C28vTj38syV3VWEZ/fYUxe283TPcr86YLcZhb3u6m8
 I0JpN+DqTf+f6r6tY4PWm19aKBFmDNEerIz8f2ajQoZdT6yunV+LwBMfhGYZnESC
 o96lZai4U+gd9XmEHxJlGQU5prfLob/hZ/NrYScCrOrQ7lX86+30M6jtEr5GwLNS
 113IWzwSwSm6yqDmDiylm4Vmk6zCmjQuaZ2YMNpk7yfWX9Mw+rNWPawf3s5k4KVe
 XP+Y0UOW3SzU8OXns0XCwNVTtJL0rPsh0zKwzLcqowrLuc3P+jJyYq58VgI3g58V
 1nLpRBGhM5VD83c27/borOQYjk5IsTxHx2jttrdgO6qsQN5C1JM=
 =GKpu
 -----END PGP SIGNATURE-----

Merge tag 'mtk-soc-for-v6.13' of https://git.kernel.org/pub/scm/linux/kernel/git/mediatek/linux into arm/drivers

MediaTek soc driver updates for v6.13

This adds support for the MT8188 SoC in the MediaTek Regulator
Coupler driver, allowing stable GPU DVFS on this chip;

Moreover, this adds a new MediaTek DVFS Resource Collector (DVFSRC)
driver, allowing to enable other drivers (interconnect, regulator)
which can now communicate with the DVFSRC hardware.

Last but not least, this includes some cleanups for the CMDQ Helper
and MediaTek SVS drivers.

* tag 'mtk-soc-for-v6.13' of https://git.kernel.org/pub/scm/linux/kernel/git/mediatek/linux:
  soc: mediatek: mtk-svs: Call of_node_put(np) only once in svs_get_subsys_device()
  soc: mediatek: mediatek-regulator-coupler: Support mt8188
  soc: mediatek: mtk-cmdq: Move cmdq_instruction init to declaration
  soc: mediatek: mtk-cmdq: Move mask build and append to function
  soc: mediatek: Add MediaTek DVFS Resource Collector (DVFSRC) driver
  dt-bindings: soc: mediatek: Add DVFSRC bindings for MT8183 and MT8195

Link: https://lore.kernel.org/r/20241104112625.161365-2-angelogioacchino.delregno@collabora.com
Signed-off-by: Arnd Bergmann <arnd@arndb.de>
This commit is contained in:
Arnd Bergmann 2024-11-04 22:48:13 +01:00
commit b8600cba23
9 changed files with 787 additions and 127 deletions

View File

@ -0,0 +1,83 @@
# SPDX-License-Identifier: (GPL-2.0 OR BSD-2-Clause)
%YAML 1.2
---
$id: http://devicetree.org/schemas/soc/mediatek/mediatek,mt8183-dvfsrc.yaml#
$schema: http://devicetree.org/meta-schemas/core.yaml#
title: MediaTek Dynamic Voltage and Frequency Scaling Resource Collector (DVFSRC)
description:
The Dynamic Voltage and Frequency Scaling Resource Collector (DVFSRC) is a
Hardware module used to collect all the requests from both software and the
various remote processors embedded into the SoC and decide about a minimum
operating voltage and a minimum DRAM frequency to fulfill those requests in
an effort to provide the best achievable performance per watt.
This hardware IP is capable of transparently performing direct register R/W
on all of the DVFSRC-controlled regulators and SoC bandwidth knobs.
maintainers:
- AngeloGioacchino Del Regno <angelogioacchino.delregno@collabora.com>
- Henry Chen <henryc.chen@mediatek.com>
properties:
compatible:
oneOf:
- enum:
- mediatek,mt8183-dvfsrc
- mediatek,mt8195-dvfsrc
- items:
- const: mediatek,mt8192-dvfsrc
- const: mediatek,mt8195-dvfsrc
reg:
maxItems: 1
description: DVFSRC common register address and length.
regulators:
type: object
$ref: /schemas/regulator/mediatek,mt6873-dvfsrc-regulator.yaml#
interconnect:
type: object
$ref: /schemas/interconnect/mediatek,mt8183-emi.yaml#
required:
- compatible
- reg
additionalProperties: false
examples:
- |
soc {
#address-cells = <2>;
#size-cells = <2>;
system-controller@10012000 {
compatible = "mediatek,mt8195-dvfsrc";
reg = <0 0x10012000 0 0x1000>;
regulators {
compatible = "mediatek,mt8195-dvfsrc-regulator";
dvfsrc_vcore: dvfsrc-vcore {
regulator-name = "dvfsrc-vcore";
regulator-min-microvolt = <550000>;
regulator-max-microvolt = <750000>;
regulator-always-on;
};
dvfsrc_vscp: dvfsrc-vscp {
regulator-name = "dvfsrc-vscp";
regulator-min-microvolt = <550000>;
regulator-max-microvolt = <750000>;
regulator-always-on;
};
};
emi_icc: interconnect {
compatible = "mediatek,mt8195-emi";
#interconnect-cells = <1>;
};
};
};

View File

@ -26,6 +26,17 @@ config MTK_DEVAPC
The violation information is logged for further analysis or
countermeasures.
config MTK_DVFSRC
tristate "MediaTek DVFSRC Support"
depends on ARCH_MEDIATEK
help
Say yes here to add support for the MediaTek Dynamic Voltage
and Frequency Scaling Resource Collector (DVFSRC): a HW
IP found on many MediaTek SoCs, which is responsible for
collecting DVFS requests from various SoC IPs, other than
software, and performing bandwidth scaling to provide the
best achievable performance-per-watt.
config MTK_INFRACFG
bool "MediaTek INFRACFG Support"
select REGMAP

View File

@ -1,6 +1,7 @@
# SPDX-License-Identifier: GPL-2.0-only
obj-$(CONFIG_MTK_CMDQ) += mtk-cmdq-helper.o
obj-$(CONFIG_MTK_DEVAPC) += mtk-devapc.o
obj-$(CONFIG_MTK_DVFSRC) += mtk-dvfsrc.o
obj-$(CONFIG_MTK_INFRACFG) += mtk-infracfg.o
obj-$(CONFIG_MTK_PMIC_WRAP) += mtk-pmic-wrap.o
obj-$(CONFIG_MTK_REGULATOR_COUPLER) += mtk-regulator-coupler.o

View File

@ -180,15 +180,23 @@ static int cmdq_pkt_append_command(struct cmdq_pkt *pkt,
return 0;
}
static int cmdq_pkt_mask(struct cmdq_pkt *pkt, u32 mask)
{
struct cmdq_instruction inst = {
.op = CMDQ_CODE_MASK,
.mask = ~mask
};
return cmdq_pkt_append_command(pkt, inst);
}
int cmdq_pkt_write(struct cmdq_pkt *pkt, u8 subsys, u16 offset, u32 value)
{
struct cmdq_instruction inst;
inst.op = CMDQ_CODE_WRITE;
inst.value = value;
inst.offset = offset;
inst.subsys = subsys;
struct cmdq_instruction inst = {
.op = CMDQ_CODE_WRITE,
.value = value,
.offset = offset,
.subsys = subsys
};
return cmdq_pkt_append_command(pkt, inst);
}
EXPORT_SYMBOL(cmdq_pkt_write);
@ -196,36 +204,30 @@ EXPORT_SYMBOL(cmdq_pkt_write);
int cmdq_pkt_write_mask(struct cmdq_pkt *pkt, u8 subsys,
u16 offset, u32 value, u32 mask)
{
struct cmdq_instruction inst = { {0} };
u16 offset_mask = offset;
int err;
if (mask != 0xffffffff) {
inst.op = CMDQ_CODE_MASK;
inst.mask = ~mask;
err = cmdq_pkt_append_command(pkt, inst);
if (mask != GENMASK(31, 0)) {
err = cmdq_pkt_mask(pkt, mask);
if (err < 0)
return err;
offset_mask |= CMDQ_WRITE_ENABLE_MASK;
}
err = cmdq_pkt_write(pkt, subsys, offset_mask, value);
return err;
return cmdq_pkt_write(pkt, subsys, offset_mask, value);
}
EXPORT_SYMBOL(cmdq_pkt_write_mask);
int cmdq_pkt_read_s(struct cmdq_pkt *pkt, u16 high_addr_reg_idx, u16 addr_low,
u16 reg_idx)
{
struct cmdq_instruction inst = {};
inst.op = CMDQ_CODE_READ_S;
inst.dst_t = CMDQ_REG_TYPE;
inst.sop = high_addr_reg_idx;
inst.reg_dst = reg_idx;
inst.src_reg = addr_low;
struct cmdq_instruction inst = {
.op = CMDQ_CODE_READ_S,
.dst_t = CMDQ_REG_TYPE,
.sop = high_addr_reg_idx,
.reg_dst = reg_idx,
.src_reg = addr_low
};
return cmdq_pkt_append_command(pkt, inst);
}
EXPORT_SYMBOL(cmdq_pkt_read_s);
@ -233,14 +235,13 @@ EXPORT_SYMBOL(cmdq_pkt_read_s);
int cmdq_pkt_write_s(struct cmdq_pkt *pkt, u16 high_addr_reg_idx,
u16 addr_low, u16 src_reg_idx)
{
struct cmdq_instruction inst = {};
inst.op = CMDQ_CODE_WRITE_S;
inst.src_t = CMDQ_REG_TYPE;
inst.sop = high_addr_reg_idx;
inst.offset = addr_low;
inst.src_reg = src_reg_idx;
struct cmdq_instruction inst = {
.op = CMDQ_CODE_WRITE_S,
.src_t = CMDQ_REG_TYPE,
.sop = high_addr_reg_idx,
.offset = addr_low,
.src_reg = src_reg_idx
};
return cmdq_pkt_append_command(pkt, inst);
}
EXPORT_SYMBOL(cmdq_pkt_write_s);
@ -248,22 +249,19 @@ EXPORT_SYMBOL(cmdq_pkt_write_s);
int cmdq_pkt_write_s_mask(struct cmdq_pkt *pkt, u16 high_addr_reg_idx,
u16 addr_low, u16 src_reg_idx, u32 mask)
{
struct cmdq_instruction inst = {};
struct cmdq_instruction inst = {
.op = CMDQ_CODE_WRITE_S_MASK,
.src_t = CMDQ_REG_TYPE,
.sop = high_addr_reg_idx,
.offset = addr_low,
.src_reg = src_reg_idx,
};
int err;
inst.op = CMDQ_CODE_MASK;
inst.mask = ~mask;
err = cmdq_pkt_append_command(pkt, inst);
err = cmdq_pkt_mask(pkt, mask);
if (err < 0)
return err;
inst.mask = 0;
inst.op = CMDQ_CODE_WRITE_S_MASK;
inst.src_t = CMDQ_REG_TYPE;
inst.sop = high_addr_reg_idx;
inst.offset = addr_low;
inst.src_reg = src_reg_idx;
return cmdq_pkt_append_command(pkt, inst);
}
EXPORT_SYMBOL(cmdq_pkt_write_s_mask);
@ -271,13 +269,12 @@ EXPORT_SYMBOL(cmdq_pkt_write_s_mask);
int cmdq_pkt_write_s_value(struct cmdq_pkt *pkt, u8 high_addr_reg_idx,
u16 addr_low, u32 value)
{
struct cmdq_instruction inst = {};
inst.op = CMDQ_CODE_WRITE_S;
inst.sop = high_addr_reg_idx;
inst.offset = addr_low;
inst.value = value;
struct cmdq_instruction inst = {
.op = CMDQ_CODE_WRITE_S,
.sop = high_addr_reg_idx,
.offset = addr_low,
.value = value
};
return cmdq_pkt_append_command(pkt, inst);
}
EXPORT_SYMBOL(cmdq_pkt_write_s_value);
@ -285,20 +282,18 @@ EXPORT_SYMBOL(cmdq_pkt_write_s_value);
int cmdq_pkt_write_s_mask_value(struct cmdq_pkt *pkt, u8 high_addr_reg_idx,
u16 addr_low, u32 value, u32 mask)
{
struct cmdq_instruction inst = {};
struct cmdq_instruction inst = {
.op = CMDQ_CODE_WRITE_S_MASK,
.sop = high_addr_reg_idx,
.offset = addr_low,
.value = value
};
int err;
inst.op = CMDQ_CODE_MASK;
inst.mask = ~mask;
err = cmdq_pkt_append_command(pkt, inst);
err = cmdq_pkt_mask(pkt, mask);
if (err < 0)
return err;
inst.op = CMDQ_CODE_WRITE_S_MASK;
inst.sop = high_addr_reg_idx;
inst.offset = addr_low;
inst.value = value;
return cmdq_pkt_append_command(pkt, inst);
}
EXPORT_SYMBOL(cmdq_pkt_write_s_mask_value);
@ -331,61 +326,61 @@ EXPORT_SYMBOL(cmdq_pkt_mem_move);
int cmdq_pkt_wfe(struct cmdq_pkt *pkt, u16 event, bool clear)
{
struct cmdq_instruction inst = { {0} };
u32 clear_option = clear ? CMDQ_WFE_UPDATE : 0;
struct cmdq_instruction inst = {
.op = CMDQ_CODE_WFE,
.value = CMDQ_WFE_OPTION | clear_option,
.event = event
};
if (event >= CMDQ_MAX_EVENT)
return -EINVAL;
inst.op = CMDQ_CODE_WFE;
inst.value = CMDQ_WFE_OPTION | clear_option;
inst.event = event;
return cmdq_pkt_append_command(pkt, inst);
}
EXPORT_SYMBOL(cmdq_pkt_wfe);
int cmdq_pkt_acquire_event(struct cmdq_pkt *pkt, u16 event)
{
struct cmdq_instruction inst = {};
struct cmdq_instruction inst = {
.op = CMDQ_CODE_WFE,
.value = CMDQ_WFE_UPDATE | CMDQ_WFE_UPDATE_VALUE | CMDQ_WFE_WAIT,
.event = event
};
if (event >= CMDQ_MAX_EVENT)
return -EINVAL;
inst.op = CMDQ_CODE_WFE;
inst.value = CMDQ_WFE_UPDATE | CMDQ_WFE_UPDATE_VALUE | CMDQ_WFE_WAIT;
inst.event = event;
return cmdq_pkt_append_command(pkt, inst);
}
EXPORT_SYMBOL(cmdq_pkt_acquire_event);
int cmdq_pkt_clear_event(struct cmdq_pkt *pkt, u16 event)
{
struct cmdq_instruction inst = { {0} };
struct cmdq_instruction inst = {
.op = CMDQ_CODE_WFE,
.value = CMDQ_WFE_UPDATE,
.event = event
};
if (event >= CMDQ_MAX_EVENT)
return -EINVAL;
inst.op = CMDQ_CODE_WFE;
inst.value = CMDQ_WFE_UPDATE;
inst.event = event;
return cmdq_pkt_append_command(pkt, inst);
}
EXPORT_SYMBOL(cmdq_pkt_clear_event);
int cmdq_pkt_set_event(struct cmdq_pkt *pkt, u16 event)
{
struct cmdq_instruction inst = {};
struct cmdq_instruction inst = {
.op = CMDQ_CODE_WFE,
.value = CMDQ_WFE_UPDATE | CMDQ_WFE_UPDATE_VALUE,
.event = event
};
if (event >= CMDQ_MAX_EVENT)
return -EINVAL;
inst.op = CMDQ_CODE_WFE;
inst.value = CMDQ_WFE_UPDATE | CMDQ_WFE_UPDATE_VALUE;
inst.event = event;
return cmdq_pkt_append_command(pkt, inst);
}
EXPORT_SYMBOL(cmdq_pkt_set_event);
@ -393,35 +388,27 @@ EXPORT_SYMBOL(cmdq_pkt_set_event);
int cmdq_pkt_poll(struct cmdq_pkt *pkt, u8 subsys,
u16 offset, u32 value)
{
struct cmdq_instruction inst = { {0} };
int err;
inst.op = CMDQ_CODE_POLL;
inst.value = value;
inst.offset = offset;
inst.subsys = subsys;
err = cmdq_pkt_append_command(pkt, inst);
return err;
struct cmdq_instruction inst = {
.op = CMDQ_CODE_POLL,
.value = value,
.offset = offset,
.subsys = subsys
};
return cmdq_pkt_append_command(pkt, inst);
}
EXPORT_SYMBOL(cmdq_pkt_poll);
int cmdq_pkt_poll_mask(struct cmdq_pkt *pkt, u8 subsys,
u16 offset, u32 value, u32 mask)
{
struct cmdq_instruction inst = { {0} };
int err;
inst.op = CMDQ_CODE_MASK;
inst.mask = ~mask;
err = cmdq_pkt_append_command(pkt, inst);
err = cmdq_pkt_mask(pkt, mask);
if (err < 0)
return err;
offset = offset | CMDQ_POLL_ENABLE_MASK;
err = cmdq_pkt_poll(pkt, subsys, offset, value);
return err;
return cmdq_pkt_poll(pkt, subsys, offset, value);
}
EXPORT_SYMBOL(cmdq_pkt_poll_mask);
@ -436,9 +423,7 @@ int cmdq_pkt_poll_addr(struct cmdq_pkt *pkt, dma_addr_t addr, u32 value, u32 mas
* which enables use_mask bit.
*/
if (mask != GENMASK(31, 0)) {
inst.op = CMDQ_CODE_MASK;
inst.mask = ~mask;
ret = cmdq_pkt_append_command(pkt, inst);
ret = cmdq_pkt_mask(pkt, mask);
if (ret < 0)
return ret;
use_mask = CMDQ_POLL_ENABLE_MASK;
@ -477,11 +462,12 @@ int cmdq_pkt_logic_command(struct cmdq_pkt *pkt, u16 result_reg_idx,
enum cmdq_logic_op s_op,
struct cmdq_operand *right_operand)
{
struct cmdq_instruction inst = { {0} };
struct cmdq_instruction inst;
if (!left_operand || !right_operand || s_op >= CMDQ_LOGIC_MAX)
return -EINVAL;
inst.value = 0;
inst.op = CMDQ_CODE_LOGIC;
inst.dst_t = CMDQ_REG_TYPE;
inst.src_t = cmdq_operand_get_type(left_operand);
@ -497,43 +483,43 @@ EXPORT_SYMBOL(cmdq_pkt_logic_command);
int cmdq_pkt_assign(struct cmdq_pkt *pkt, u16 reg_idx, u32 value)
{
struct cmdq_instruction inst = {};
inst.op = CMDQ_CODE_LOGIC;
inst.dst_t = CMDQ_REG_TYPE;
inst.reg_dst = reg_idx;
inst.value = value;
struct cmdq_instruction inst = {
.op = CMDQ_CODE_LOGIC,
.dst_t = CMDQ_REG_TYPE,
.reg_dst = reg_idx,
.value = value
};
return cmdq_pkt_append_command(pkt, inst);
}
EXPORT_SYMBOL(cmdq_pkt_assign);
int cmdq_pkt_jump_abs(struct cmdq_pkt *pkt, dma_addr_t addr, u8 shift_pa)
{
struct cmdq_instruction inst = {};
inst.op = CMDQ_CODE_JUMP;
inst.offset = CMDQ_JUMP_ABSOLUTE;
inst.value = addr >> shift_pa;
struct cmdq_instruction inst = {
.op = CMDQ_CODE_JUMP,
.offset = CMDQ_JUMP_ABSOLUTE,
.value = addr >> shift_pa
};
return cmdq_pkt_append_command(pkt, inst);
}
EXPORT_SYMBOL(cmdq_pkt_jump_abs);
int cmdq_pkt_jump_rel(struct cmdq_pkt *pkt, s32 offset, u8 shift_pa)
{
struct cmdq_instruction inst = { {0} };
inst.op = CMDQ_CODE_JUMP;
inst.value = (u32)offset >> shift_pa;
struct cmdq_instruction inst = {
.op = CMDQ_CODE_JUMP,
.value = (u32)offset >> shift_pa
};
return cmdq_pkt_append_command(pkt, inst);
}
EXPORT_SYMBOL(cmdq_pkt_jump_rel);
int cmdq_pkt_eoc(struct cmdq_pkt *pkt)
{
struct cmdq_instruction inst = { {0} };
inst.op = CMDQ_CODE_EOC;
inst.value = CMDQ_EOC_IRQ_EN;
struct cmdq_instruction inst = {
.op = CMDQ_CODE_EOC,
.value = CMDQ_EOC_IRQ_EN
};
return cmdq_pkt_append_command(pkt, inst);
}
EXPORT_SYMBOL(cmdq_pkt_eoc);
@ -544,9 +530,7 @@ int cmdq_pkt_finalize(struct cmdq_pkt *pkt)
int err;
/* insert EOC and generate IRQ for each command iteration */
inst.op = CMDQ_CODE_EOC;
inst.value = CMDQ_EOC_IRQ_EN;
err = cmdq_pkt_append_command(pkt, inst);
err = cmdq_pkt_eoc(pkt);
if (err < 0)
return err;
@ -554,9 +538,7 @@ int cmdq_pkt_finalize(struct cmdq_pkt *pkt)
inst.op = CMDQ_CODE_JUMP;
inst.value = CMDQ_JUMP_PASS >>
cmdq_get_shift_pa(((struct cmdq_client *)pkt->cl)->chan);
err = cmdq_pkt_append_command(pkt, inst);
return err;
return cmdq_pkt_append_command(pkt, inst);
}
EXPORT_SYMBOL(cmdq_pkt_finalize);

View File

@ -0,0 +1,545 @@
// SPDX-License-Identifier: GPL-2.0
/*
* Copyright (C) 2021 MediaTek Inc.
* Copyright (c) 2024 Collabora Ltd.
* AngeloGioacchino Del Regno <angelogioacchino.delregno@collabora.com>
*/
#include <linux/arm-smccc.h>
#include <linux/bitfield.h>
#include <linux/iopoll.h>
#include <linux/module.h>
#include <linux/of.h>
#include <linux/of_platform.h>
#include <linux/platform_device.h>
#include <linux/soc/mediatek/dvfsrc.h>
#include <linux/soc/mediatek/mtk_sip_svc.h>
/* DVFSRC_LEVEL */
#define DVFSRC_V1_LEVEL_TARGET_LEVEL GENMASK(15, 0)
#define DVFSRC_TGT_LEVEL_IDLE 0x00
#define DVFSRC_V1_LEVEL_CURRENT_LEVEL GENMASK(31, 16)
/* DVFSRC_SW_REQ, DVFSRC_SW_REQ2 */
#define DVFSRC_V1_SW_REQ2_DRAM_LEVEL GENMASK(1, 0)
#define DVFSRC_V1_SW_REQ2_VCORE_LEVEL GENMASK(3, 2)
#define DVFSRC_V2_SW_REQ_DRAM_LEVEL GENMASK(3, 0)
#define DVFSRC_V2_SW_REQ_VCORE_LEVEL GENMASK(6, 4)
/* DVFSRC_VCORE */
#define DVFSRC_V2_VCORE_REQ_VSCP_LEVEL GENMASK(14, 12)
#define DVFSRC_POLL_TIMEOUT_US 1000
#define STARTUP_TIME_US 1
#define MTK_SIP_DVFSRC_INIT 0x0
#define MTK_SIP_DVFSRC_START 0x1
struct dvfsrc_bw_constraints {
u16 max_dram_nom_bw;
u16 max_dram_peak_bw;
u16 max_dram_hrt_bw;
};
struct dvfsrc_opp {
u32 vcore_opp;
u32 dram_opp;
};
struct dvfsrc_opp_desc {
const struct dvfsrc_opp *opps;
u32 num_opp;
};
struct dvfsrc_soc_data;
struct mtk_dvfsrc {
struct device *dev;
struct platform_device *icc;
struct platform_device *regulator;
const struct dvfsrc_soc_data *dvd;
const struct dvfsrc_opp_desc *curr_opps;
void __iomem *regs;
int dram_type;
};
struct dvfsrc_soc_data {
const int *regs;
const struct dvfsrc_opp_desc *opps_desc;
u32 (*get_target_level)(struct mtk_dvfsrc *dvfsrc);
u32 (*get_current_level)(struct mtk_dvfsrc *dvfsrc);
u32 (*get_vcore_level)(struct mtk_dvfsrc *dvfsrc);
u32 (*get_vscp_level)(struct mtk_dvfsrc *dvfsrc);
void (*set_dram_bw)(struct mtk_dvfsrc *dvfsrc, u64 bw);
void (*set_dram_peak_bw)(struct mtk_dvfsrc *dvfsrc, u64 bw);
void (*set_dram_hrt_bw)(struct mtk_dvfsrc *dvfsrc, u64 bw);
void (*set_opp_level)(struct mtk_dvfsrc *dvfsrc, u32 level);
void (*set_vcore_level)(struct mtk_dvfsrc *dvfsrc, u32 level);
void (*set_vscp_level)(struct mtk_dvfsrc *dvfsrc, u32 level);
int (*wait_for_opp_level)(struct mtk_dvfsrc *dvfsrc, u32 level);
int (*wait_for_vcore_level)(struct mtk_dvfsrc *dvfsrc, u32 level);
const struct dvfsrc_bw_constraints *bw_constraints;
};
static u32 dvfsrc_readl(struct mtk_dvfsrc *dvfs, u32 offset)
{
return readl(dvfs->regs + dvfs->dvd->regs[offset]);
}
static void dvfsrc_writel(struct mtk_dvfsrc *dvfs, u32 offset, u32 val)
{
writel(val, dvfs->regs + dvfs->dvd->regs[offset]);
}
enum dvfsrc_regs {
DVFSRC_SW_REQ,
DVFSRC_SW_REQ2,
DVFSRC_LEVEL,
DVFSRC_TARGET_LEVEL,
DVFSRC_SW_BW,
DVFSRC_SW_PEAK_BW,
DVFSRC_SW_HRT_BW,
DVFSRC_VCORE,
DVFSRC_REGS_MAX,
};
static const int dvfsrc_mt8183_regs[] = {
[DVFSRC_SW_REQ] = 0x4,
[DVFSRC_SW_REQ2] = 0x8,
[DVFSRC_LEVEL] = 0xDC,
[DVFSRC_SW_BW] = 0x160,
};
static const int dvfsrc_mt8195_regs[] = {
[DVFSRC_SW_REQ] = 0xc,
[DVFSRC_VCORE] = 0x6c,
[DVFSRC_SW_PEAK_BW] = 0x278,
[DVFSRC_SW_BW] = 0x26c,
[DVFSRC_SW_HRT_BW] = 0x290,
[DVFSRC_LEVEL] = 0xd44,
[DVFSRC_TARGET_LEVEL] = 0xd48,
};
static const struct dvfsrc_opp *dvfsrc_get_current_opp(struct mtk_dvfsrc *dvfsrc)
{
u32 level = dvfsrc->dvd->get_current_level(dvfsrc);
return &dvfsrc->curr_opps->opps[level];
}
static bool dvfsrc_is_idle(struct mtk_dvfsrc *dvfsrc)
{
if (!dvfsrc->dvd->get_target_level)
return true;
return dvfsrc->dvd->get_target_level(dvfsrc) == DVFSRC_TGT_LEVEL_IDLE;
}
static int dvfsrc_wait_for_vcore_level_v1(struct mtk_dvfsrc *dvfsrc, u32 level)
{
const struct dvfsrc_opp *curr;
return readx_poll_timeout_atomic(dvfsrc_get_current_opp, dvfsrc, curr,
curr->vcore_opp >= level, STARTUP_TIME_US,
DVFSRC_POLL_TIMEOUT_US);
}
static int dvfsrc_wait_for_opp_level_v1(struct mtk_dvfsrc *dvfsrc, u32 level)
{
const struct dvfsrc_opp *target, *curr;
int ret;
target = &dvfsrc->curr_opps->opps[level];
ret = readx_poll_timeout_atomic(dvfsrc_get_current_opp, dvfsrc, curr,
curr->dram_opp >= target->dram_opp &&
curr->vcore_opp >= target->vcore_opp,
STARTUP_TIME_US, DVFSRC_POLL_TIMEOUT_US);
if (ret < 0) {
dev_warn(dvfsrc->dev,
"timeout! target OPP: %u, dram: %d, vcore: %d\n", level,
curr->dram_opp, curr->vcore_opp);
return ret;
}
return 0;
}
static int dvfsrc_wait_for_opp_level_v2(struct mtk_dvfsrc *dvfsrc, u32 level)
{
const struct dvfsrc_opp *target, *curr;
int ret;
target = &dvfsrc->curr_opps->opps[level];
ret = readx_poll_timeout_atomic(dvfsrc_get_current_opp, dvfsrc, curr,
curr->dram_opp >= target->dram_opp &&
curr->vcore_opp >= target->vcore_opp,
STARTUP_TIME_US, DVFSRC_POLL_TIMEOUT_US);
if (ret < 0) {
dev_warn(dvfsrc->dev,
"timeout! target OPP: %u, dram: %d\n", level, curr->dram_opp);
return ret;
}
return 0;
}
static u32 dvfsrc_get_target_level_v1(struct mtk_dvfsrc *dvfsrc)
{
u32 val = dvfsrc_readl(dvfsrc, DVFSRC_LEVEL);
return FIELD_GET(DVFSRC_V1_LEVEL_TARGET_LEVEL, val);
}
static u32 dvfsrc_get_current_level_v1(struct mtk_dvfsrc *dvfsrc)
{
u32 val = dvfsrc_readl(dvfsrc, DVFSRC_LEVEL);
u32 current_level = FIELD_GET(DVFSRC_V1_LEVEL_CURRENT_LEVEL, val);
return ffs(current_level) - 1;
}
static u32 dvfsrc_get_target_level_v2(struct mtk_dvfsrc *dvfsrc)
{
return dvfsrc_readl(dvfsrc, DVFSRC_TARGET_LEVEL);
}
static u32 dvfsrc_get_current_level_v2(struct mtk_dvfsrc *dvfsrc)
{
u32 val = dvfsrc_readl(dvfsrc, DVFSRC_LEVEL);
u32 level = ffs(val);
/* Valid levels */
if (level < dvfsrc->curr_opps->num_opp)
return dvfsrc->curr_opps->num_opp - level;
/* Zero for level 0 or invalid level */
return 0;
}
static u32 dvfsrc_get_vcore_level_v1(struct mtk_dvfsrc *dvfsrc)
{
u32 val = dvfsrc_readl(dvfsrc, DVFSRC_SW_REQ2);
return FIELD_GET(DVFSRC_V1_SW_REQ2_VCORE_LEVEL, val);
}
static void dvfsrc_set_vcore_level_v1(struct mtk_dvfsrc *dvfsrc, u32 level)
{
u32 val = dvfsrc_readl(dvfsrc, DVFSRC_SW_REQ2);
val &= ~DVFSRC_V1_SW_REQ2_VCORE_LEVEL;
val |= FIELD_PREP(DVFSRC_V1_SW_REQ2_VCORE_LEVEL, level);
dvfsrc_writel(dvfsrc, DVFSRC_SW_REQ2, val);
}
static u32 dvfsrc_get_vcore_level_v2(struct mtk_dvfsrc *dvfsrc)
{
u32 val = dvfsrc_readl(dvfsrc, DVFSRC_SW_REQ);
return FIELD_GET(DVFSRC_V2_SW_REQ_VCORE_LEVEL, val);
}
static void dvfsrc_set_vcore_level_v2(struct mtk_dvfsrc *dvfsrc, u32 level)
{
u32 val = dvfsrc_readl(dvfsrc, DVFSRC_SW_REQ);
val &= ~DVFSRC_V2_SW_REQ_VCORE_LEVEL;
val |= FIELD_PREP(DVFSRC_V2_SW_REQ_VCORE_LEVEL, level);
dvfsrc_writel(dvfsrc, DVFSRC_SW_REQ, val);
}
static u32 dvfsrc_get_vscp_level_v2(struct mtk_dvfsrc *dvfsrc)
{
u32 val = dvfsrc_readl(dvfsrc, DVFSRC_VCORE);
return FIELD_GET(DVFSRC_V2_VCORE_REQ_VSCP_LEVEL, val);
}
static void dvfsrc_set_vscp_level_v2(struct mtk_dvfsrc *dvfsrc, u32 level)
{
u32 val = dvfsrc_readl(dvfsrc, DVFSRC_VCORE);
val &= ~DVFSRC_V2_VCORE_REQ_VSCP_LEVEL;
val |= FIELD_PREP(DVFSRC_V2_VCORE_REQ_VSCP_LEVEL, level);
dvfsrc_writel(dvfsrc, DVFSRC_VCORE, val);
}
static void __dvfsrc_set_dram_bw_v1(struct mtk_dvfsrc *dvfsrc, u32 reg,
u16 max_bw, u16 min_bw, u64 bw)
{
u32 new_bw = (u32)div_u64(bw, 100 * 1000);
/* If bw constraints (in mbps) are defined make sure to respect them */
if (max_bw)
new_bw = min(new_bw, max_bw);
if (min_bw && new_bw > 0)
new_bw = max(new_bw, min_bw);
dvfsrc_writel(dvfsrc, reg, new_bw);
}
static void dvfsrc_set_dram_bw_v1(struct mtk_dvfsrc *dvfsrc, u64 bw)
{
u64 max_bw = dvfsrc->dvd->bw_constraints->max_dram_nom_bw;
__dvfsrc_set_dram_bw_v1(dvfsrc, DVFSRC_SW_BW, max_bw, 0, bw);
};
static void dvfsrc_set_dram_peak_bw_v1(struct mtk_dvfsrc *dvfsrc, u64 bw)
{
u64 max_bw = dvfsrc->dvd->bw_constraints->max_dram_peak_bw;
__dvfsrc_set_dram_bw_v1(dvfsrc, DVFSRC_SW_PEAK_BW, max_bw, 0, bw);
}
static void dvfsrc_set_dram_hrt_bw_v1(struct mtk_dvfsrc *dvfsrc, u64 bw)
{
u64 max_bw = dvfsrc->dvd->bw_constraints->max_dram_hrt_bw;
__dvfsrc_set_dram_bw_v1(dvfsrc, DVFSRC_SW_HRT_BW, max_bw, 0, bw);
}
static void dvfsrc_set_opp_level_v1(struct mtk_dvfsrc *dvfsrc, u32 level)
{
const struct dvfsrc_opp *opp = &dvfsrc->curr_opps->opps[level];
u32 val;
/* Translate Pstate to DVFSRC level and set it to DVFSRC HW */
val = FIELD_PREP(DVFSRC_V1_SW_REQ2_DRAM_LEVEL, opp->dram_opp);
val |= FIELD_PREP(DVFSRC_V1_SW_REQ2_VCORE_LEVEL, opp->vcore_opp);
dev_dbg(dvfsrc->dev, "vcore_opp: %d, dram_opp: %d\n", opp->vcore_opp, opp->dram_opp);
dvfsrc_writel(dvfsrc, DVFSRC_SW_REQ, val);
}
int mtk_dvfsrc_send_request(const struct device *dev, u32 cmd, u64 data)
{
struct mtk_dvfsrc *dvfsrc = dev_get_drvdata(dev);
bool state;
int ret;
dev_dbg(dvfsrc->dev, "cmd: %d, data: %llu\n", cmd, data);
switch (cmd) {
case MTK_DVFSRC_CMD_BW:
dvfsrc->dvd->set_dram_bw(dvfsrc, data);
return 0;
case MTK_DVFSRC_CMD_HRT_BW:
if (dvfsrc->dvd->set_dram_hrt_bw)
dvfsrc->dvd->set_dram_hrt_bw(dvfsrc, data);
return 0;
case MTK_DVFSRC_CMD_PEAK_BW:
if (dvfsrc->dvd->set_dram_peak_bw)
dvfsrc->dvd->set_dram_peak_bw(dvfsrc, data);
return 0;
case MTK_DVFSRC_CMD_OPP:
if (!dvfsrc->dvd->set_opp_level)
return 0;
dvfsrc->dvd->set_opp_level(dvfsrc, data);
break;
case MTK_DVFSRC_CMD_VCORE_LEVEL:
dvfsrc->dvd->set_vcore_level(dvfsrc, data);
break;
case MTK_DVFSRC_CMD_VSCP_LEVEL:
if (!dvfsrc->dvd->set_vscp_level)
return 0;
dvfsrc->dvd->set_vscp_level(dvfsrc, data);
break;
default:
dev_err(dvfsrc->dev, "unknown command: %d\n", cmd);
return -EOPNOTSUPP;
}
/* DVFSRC needs at least 2T(~196ns) to handle a request */
udelay(STARTUP_TIME_US);
ret = readx_poll_timeout_atomic(dvfsrc_is_idle, dvfsrc, state, state,
STARTUP_TIME_US, DVFSRC_POLL_TIMEOUT_US);
if (ret < 0) {
dev_warn(dvfsrc->dev,
"%d: idle timeout, data: %llu, last: %d -> %d\n", cmd, data,
dvfsrc->dvd->get_current_level(dvfsrc),
dvfsrc->dvd->get_target_level(dvfsrc));
return ret;
}
if (cmd == MTK_DVFSRC_CMD_OPP)
ret = dvfsrc->dvd->wait_for_opp_level(dvfsrc, data);
else
ret = dvfsrc->dvd->wait_for_vcore_level(dvfsrc, data);
if (ret < 0) {
dev_warn(dvfsrc->dev,
"%d: wait timeout, data: %llu, last: %d -> %d\n",
cmd, data,
dvfsrc->dvd->get_current_level(dvfsrc),
dvfsrc->dvd->get_target_level(dvfsrc));
return ret;
}
return 0;
}
EXPORT_SYMBOL(mtk_dvfsrc_send_request);
int mtk_dvfsrc_query_info(const struct device *dev, u32 cmd, int *data)
{
struct mtk_dvfsrc *dvfsrc = dev_get_drvdata(dev);
switch (cmd) {
case MTK_DVFSRC_CMD_VCORE_LEVEL:
*data = dvfsrc->dvd->get_vcore_level(dvfsrc);
break;
case MTK_DVFSRC_CMD_VSCP_LEVEL:
*data = dvfsrc->dvd->get_vscp_level(dvfsrc);
break;
default:
return -EOPNOTSUPP;
}
return 0;
}
EXPORT_SYMBOL(mtk_dvfsrc_query_info);
static int mtk_dvfsrc_probe(struct platform_device *pdev)
{
struct arm_smccc_res ares;
struct mtk_dvfsrc *dvfsrc;
int ret;
dvfsrc = devm_kzalloc(&pdev->dev, sizeof(*dvfsrc), GFP_KERNEL);
if (!dvfsrc)
return -ENOMEM;
dvfsrc->dvd = of_device_get_match_data(&pdev->dev);
dvfsrc->dev = &pdev->dev;
dvfsrc->regs = devm_platform_get_and_ioremap_resource(pdev, 0, NULL);
if (IS_ERR(dvfsrc->regs))
return PTR_ERR(dvfsrc->regs);
arm_smccc_smc(MTK_SIP_DVFSRC_VCOREFS_CONTROL, MTK_SIP_DVFSRC_INIT,
0, 0, 0, 0, 0, 0, &ares);
if (ares.a0)
return dev_err_probe(&pdev->dev, -EINVAL, "DVFSRC init failed: %lu\n", ares.a0);
dvfsrc->dram_type = ares.a1;
dev_dbg(&pdev->dev, "DRAM Type: %d\n", dvfsrc->dram_type);
dvfsrc->curr_opps = &dvfsrc->dvd->opps_desc[dvfsrc->dram_type];
platform_set_drvdata(pdev, dvfsrc);
ret = devm_of_platform_populate(&pdev->dev);
if (ret)
return dev_err_probe(&pdev->dev, ret, "Failed to populate child devices\n");
/* Everything is set up - make it run! */
arm_smccc_smc(MTK_SIP_DVFSRC_VCOREFS_CONTROL, MTK_SIP_DVFSRC_START,
0, 0, 0, 0, 0, 0, &ares);
if (ares.a0)
return dev_err_probe(&pdev->dev, -EINVAL, "Cannot start DVFSRC: %lu\n", ares.a0);
return 0;
}
static const struct dvfsrc_opp dvfsrc_opp_mt8183_lp4[] = {
{ 0, 0 }, { 0, 1 }, { 0, 2 }, { 1, 2 },
};
static const struct dvfsrc_opp dvfsrc_opp_mt8183_lp3[] = {
{ 0, 0 }, { 0, 1 }, { 1, 1 }, { 1, 2 },
};
static const struct dvfsrc_opp_desc dvfsrc_opp_mt8183_desc[] = {
[0] = {
.opps = dvfsrc_opp_mt8183_lp4,
.num_opp = ARRAY_SIZE(dvfsrc_opp_mt8183_lp4),
},
[1] = {
.opps = dvfsrc_opp_mt8183_lp3,
.num_opp = ARRAY_SIZE(dvfsrc_opp_mt8183_lp3),
},
[2] = {
.opps = dvfsrc_opp_mt8183_lp3,
.num_opp = ARRAY_SIZE(dvfsrc_opp_mt8183_lp3),
}
};
static const struct dvfsrc_bw_constraints dvfsrc_bw_constr_mt8183 = { 0, 0, 0 };
static const struct dvfsrc_soc_data mt8183_data = {
.opps_desc = dvfsrc_opp_mt8183_desc,
.regs = dvfsrc_mt8183_regs,
.get_target_level = dvfsrc_get_target_level_v1,
.get_current_level = dvfsrc_get_current_level_v1,
.get_vcore_level = dvfsrc_get_vcore_level_v1,
.set_dram_bw = dvfsrc_set_dram_bw_v1,
.set_opp_level = dvfsrc_set_opp_level_v1,
.set_vcore_level = dvfsrc_set_vcore_level_v1,
.wait_for_opp_level = dvfsrc_wait_for_opp_level_v1,
.wait_for_vcore_level = dvfsrc_wait_for_vcore_level_v1,
.bw_constraints = &dvfsrc_bw_constr_mt8183,
};
static const struct dvfsrc_opp dvfsrc_opp_mt8195_lp4[] = {
{ 0, 0 }, { 1, 0 }, { 2, 0 }, { 3, 0 },
{ 0, 1 }, { 1, 1 }, { 2, 1 }, { 3, 1 },
{ 0, 2 }, { 1, 2 }, { 2, 2 }, { 3, 2 },
{ 1, 3 }, { 2, 3 }, { 3, 3 }, { 1, 4 },
{ 2, 4 }, { 3, 4 }, { 2, 5 }, { 3, 5 },
{ 3, 6 },
};
static const struct dvfsrc_opp_desc dvfsrc_opp_mt8195_desc[] = {
[0] = {
.opps = dvfsrc_opp_mt8195_lp4,
.num_opp = ARRAY_SIZE(dvfsrc_opp_mt8195_lp4),
}
};
static const struct dvfsrc_bw_constraints dvfsrc_bw_constr_mt8195 = {
.max_dram_nom_bw = 255,
.max_dram_peak_bw = 255,
.max_dram_hrt_bw = 1023,
};
static const struct dvfsrc_soc_data mt8195_data = {
.opps_desc = dvfsrc_opp_mt8195_desc,
.regs = dvfsrc_mt8195_regs,
.get_target_level = dvfsrc_get_target_level_v2,
.get_current_level = dvfsrc_get_current_level_v2,
.get_vcore_level = dvfsrc_get_vcore_level_v2,
.get_vscp_level = dvfsrc_get_vscp_level_v2,
.set_dram_bw = dvfsrc_set_dram_bw_v1,
.set_dram_peak_bw = dvfsrc_set_dram_peak_bw_v1,
.set_dram_hrt_bw = dvfsrc_set_dram_hrt_bw_v1,
.set_vcore_level = dvfsrc_set_vcore_level_v2,
.set_vscp_level = dvfsrc_set_vscp_level_v2,
.wait_for_opp_level = dvfsrc_wait_for_opp_level_v2,
.wait_for_vcore_level = dvfsrc_wait_for_vcore_level_v1,
.bw_constraints = &dvfsrc_bw_constr_mt8195,
};
static const struct of_device_id mtk_dvfsrc_of_match[] = {
{ .compatible = "mediatek,mt8183-dvfsrc", .data = &mt8183_data },
{ .compatible = "mediatek,mt8195-dvfsrc", .data = &mt8195_data },
{ /* sentinel */ }
};
static struct platform_driver mtk_dvfsrc_driver = {
.probe = mtk_dvfsrc_probe,
.driver = {
.name = "mtk-dvfsrc",
.of_match_table = mtk_dvfsrc_of_match,
},
};
module_platform_driver(mtk_dvfsrc_driver);
MODULE_AUTHOR("AngeloGioacchino Del Regno <angelogioacchino.delregno@collabora.com>");
MODULE_AUTHOR("Dawei Chien <dawei.chien@mediatek.com>");
MODULE_LICENSE("GPL");
MODULE_DESCRIPTION("MediaTek DVFSRC driver");

View File

@ -147,6 +147,7 @@ static int mediatek_regulator_coupler_init(void)
{
if (!of_machine_is_compatible("mediatek,mt8183") &&
!of_machine_is_compatible("mediatek,mt8186") &&
!of_machine_is_compatible("mediatek,mt8188") &&
!of_machine_is_compatible("mediatek,mt8192"))
return 0;

View File

@ -2133,14 +2133,12 @@ static struct device *svs_get_subsys_device(struct svs_platform *svsp,
}
pdev = of_find_device_by_node(np);
of_node_put(np);
if (!pdev) {
of_node_put(np);
dev_err(svsp->dev, "cannot find pdev by %s\n", node_name);
return ERR_PTR(-ENXIO);
}
of_node_put(np);
return &pdev->dev;
}

View File

@ -0,0 +1,36 @@
/* SPDX-License-Identifier: GPL-2.0
*
* Copyright (c) 2021 MediaTek Inc.
* Copyright (c) 2024 Collabora Ltd.
* AngeloGioacchino Del Regno <angelogioacchino.delregno@collabora.com>
*/
#ifndef __MEDIATEK_DVFSRC_H
#define __MEDIATEK_DVFSRC_H
enum mtk_dvfsrc_cmd {
MTK_DVFSRC_CMD_BW,
MTK_DVFSRC_CMD_HRT_BW,
MTK_DVFSRC_CMD_PEAK_BW,
MTK_DVFSRC_CMD_OPP,
MTK_DVFSRC_CMD_VCORE_LEVEL,
MTK_DVFSRC_CMD_VSCP_LEVEL,
MTK_DVFSRC_CMD_MAX,
};
#if IS_ENABLED(CONFIG_MTK_DVFSRC)
int mtk_dvfsrc_send_request(const struct device *dev, u32 cmd, u64 data);
int mtk_dvfsrc_query_info(const struct device *dev, u32 cmd, int *data);
#else
static inline int mtk_dvfsrc_send_request(const struct device *dev, u32 cmd, u64 data)
{ return -ENODEV; }
static inline int mtk_dvfsrc_query_info(const struct device *dev, u32 cmd, int *data)
{ return -ENODEV; }
#endif /* CONFIG_MTK_DVFSRC */
#endif

View File

@ -22,6 +22,9 @@
ARM_SMCCC_CALL_VAL(ARM_SMCCC_FAST_CALL, MTK_SIP_SMC_CONVENTION, \
ARM_SMCCC_OWNER_SIP, fn_id)
/* DVFSRC SMC calls */
#define MTK_SIP_DVFSRC_VCOREFS_CONTROL MTK_SIP_SMC_CMD(0x506)
/* IOMMU related SMC call */
#define MTK_SIP_KERNEL_IOMMU_CONTROL MTK_SIP_SMC_CMD(0x514)