* [PATCH v6 04/17] media: rkisp1: add Rockchip MIPI Synopsys DPHY driver
From: Jacob Chen @ 2018-05-16 14:53 UTC (permalink / raw)
To: linux-arm-kernel
In-Reply-To: <CAFLEztRY0xSScE51uvUtS89PqE_bNjkMfzBeTQTPyKd6asfPEQ@mail.gmail.com>
2018-05-16 22:39 GMT+08:00 Jacob Chen <jacobchen110@gmail.com>:
> Hi Laurent,
>
> 2018-05-16 13:20 GMT+08:00 Laurent Pinchart <laurent.pinchart@ideasonboard.com>:
>> Hi Jacob,
>>
>> Thank you for the patch.
>>
>> On Thursday, 8 March 2018 11:47:54 EEST Jacob Chen wrote:
>>> From: Jacob Chen <jacob2.chen@rock-chips.com>
>>>
>>> This commit adds a subdev driver for Rockchip MIPI Synopsys DPHY driver
>>
>> Should this really be a subdev driver ? After a quick look at the code, the
>> only parameters you need to configure the PHY is the number of lanes and the
>> data rate. Implementing the whole subdev API seems overcomplicated to me,
>> especially given that the D-PHY doesn't deal with video streams as such, but
>> operates one level down. Shouldn't we model the D-PHY using the Linux PHY
>> framework ? I believe all the features you need are there except for a D-PHY-
>> specific configuration function that should be very easy to add.
>>
>
> It deserves a subdev driver since the ISP is not the only user.
> Other driver, like VIP, use it too.
>
>
For example, if there are two sensors connected to a rk3399 board.
Sensor1 --> DPHY1
Sensor2 --> DPHY2
With a subdev phy driver, i can choose either ISP or VIP for
sensor1/sensor2 by enable/disable media link in the run time.
1.
Sensor1 --> DPHY1 ---> VIP
Sensor2 --> DPHY2 ---> ISP1
2.
Sensor1 --> DPHY1 ---> ISP1
Sensor2 --> DPHY2 ---> VIP
>>> Signed-off-by: Jacob Chen <jacob2.chen@rock-chips.com>
>>> Signed-off-by: Shunqian Zheng <zhengsq@rock-chips.com>
>>> Signed-off-by: Tomasz Figa <tfiga@chromium.org>
>>> ---
>>> .../media/platform/rockchip/isp1/mipi_dphy_sy.c | 868 ++++++++++++++++++
>>> .../media/platform/rockchip/isp1/mipi_dphy_sy.h | 15 +
>>> 2 files changed, 883 insertions(+)
>>> create mode 100644 drivers/media/platform/rockchip/isp1/mipi_dphy_sy.c
>>> create mode 100644 drivers/media/platform/rockchip/isp1/mipi_dphy_sy.h
>>>
>>> diff --git a/drivers/media/platform/rockchip/isp1/mipi_dphy_sy.c
>>> b/drivers/media/platform/rockchip/isp1/mipi_dphy_sy.c new file mode 100644
>>> index 000000000000..32140960557a
>>> --- /dev/null
>>> +++ b/drivers/media/platform/rockchip/isp1/mipi_dphy_sy.c
>>> @@ -0,0 +1,868 @@
>>> +// SPDX-License-Identifier: (GPL-2.0+ OR MIT)
>>> +/*
>>> + * Rockchip MIPI Synopsys DPHY driver
>>> + *
>>> + * Copyright (C) 2017 Fuzhou Rockchip Electronics Co., Ltd.
>>> + */
>>> +
>>> +#include <linux/clk.h>
>>> +#include <linux/delay.h>
>>> +#include <linux/module.h>
>>> +#include <linux/of.h>
>>> +#include <linux/of_platform.h>
>>> +#include <linux/platform_device.h>
>>> +#include <linux/pm_runtime.h>
>>> +#include <linux/regmap.h>
>>> +#include <linux/mfd/syscon.h>
>>> +#include <media/media-entity.h>
>>> +#include <media/v4l2-ctrls.h>
>>> +#include <media/v4l2-fwnode.h>
>>> +#include <media/v4l2-subdev.h>
>>> +
>>> +#define RK3288_GRF_SOC_CON6 0x025c
>>> +#define RK3288_GRF_SOC_CON8 0x0264
>>> +#define RK3288_GRF_SOC_CON9 0x0268
>>> +#define RK3288_GRF_SOC_CON10 0x026c
>>> +#define RK3288_GRF_SOC_CON14 0x027c
>>> +#define RK3288_GRF_SOC_STATUS21 0x02d4
>>> +#define RK3288_GRF_IO_VSEL 0x0380
>>> +#define RK3288_GRF_SOC_CON15 0x03a4
>>> +
>>> +#define RK3399_GRF_SOC_CON9 0x6224
>>> +#define RK3399_GRF_SOC_CON21 0x6254
>>> +#define RK3399_GRF_SOC_CON22 0x6258
>>> +#define RK3399_GRF_SOC_CON23 0x625c
>>> +#define RK3399_GRF_SOC_CON24 0x6260
>>> +#define RK3399_GRF_SOC_CON25 0x6264
>>> +#define RK3399_GRF_SOC_STATUS1 0xe2a4
>>> +
>>> +#define CLOCK_LANE_HS_RX_CONTROL 0x34
>>> +#define LANE0_HS_RX_CONTROL 0x44
>>> +#define LANE1_HS_RX_CONTROL 0x54
>>> +#define LANE2_HS_RX_CONTROL 0x84
>>> +#define LANE3_HS_RX_CONTROL 0x94
>>> +#define HS_RX_DATA_LANES_THS_SETTLE_CONTROL 0x75
>>> +
>>> +/*
>>> + * CSI HOST
>>> + */
>>> +#define CSIHOST_PHY_TEST_CTRL0 0x30
>>> +#define CSIHOST_PHY_TEST_CTRL1 0x34
>>> +#define CSIHOST_PHY_SHUTDOWNZ 0x08
>>> +#define CSIHOST_DPHY_RSTZ 0x0c
>>> +
>>> +#define PHY_TESTEN_ADDR (0x1 << 16)
>>> +#define PHY_TESTEN_DATA (0x0 << 16)
>>> +#define PHY_TESTCLK (0x1 << 1)
>>> +#define PHY_TESTCLR (0x1 << 0)
>>> +#define THS_SETTLE_COUNTER_THRESHOLD 0x04
>>> +
>>> +#define HIWORD_UPDATE(val, mask, shift) \
>>> + ((val) << (shift) | (mask) << ((shift) + 16))
>>> +
>>> +enum mipi_dphy_sy_pads {
>>> + MIPI_DPHY_SY_PAD_SINK = 0,
>>> + MIPI_DPHY_SY_PAD_SOURCE,
>>> + MIPI_DPHY_SY_PADS_NUM,
>>> +};
>>> +
>>> +enum dphy_reg_id {
>>> + GRF_DPHY_RX0_TURNDISABLE = 0,
>>> + GRF_DPHY_RX0_FORCERXMODE,
>>> + GRF_DPHY_RX0_FORCETXSTOPMODE,
>>> + GRF_DPHY_RX0_ENABLE,
>>> + GRF_DPHY_RX0_TESTCLR,
>>> + GRF_DPHY_RX0_TESTCLK,
>>> + GRF_DPHY_RX0_TESTEN,
>>> + GRF_DPHY_RX0_TESTDIN,
>>> + GRF_DPHY_RX0_TURNREQUEST,
>>> + GRF_DPHY_RX0_TESTDOUT,
>>> + GRF_DPHY_TX0_TURNDISABLE,
>>> + GRF_DPHY_TX0_FORCERXMODE,
>>> + GRF_DPHY_TX0_FORCETXSTOPMODE,
>>> + GRF_DPHY_TX0_TURNREQUEST,
>>> + GRF_DPHY_TX1RX1_TURNDISABLE,
>>> + GRF_DPHY_TX1RX1_FORCERXMODE,
>>> + GRF_DPHY_TX1RX1_FORCETXSTOPMODE,
>>> + GRF_DPHY_TX1RX1_ENABLE,
>>> + GRF_DPHY_TX1RX1_MASTERSLAVEZ,
>>> + GRF_DPHY_TX1RX1_BASEDIR,
>>> + GRF_DPHY_TX1RX1_ENABLECLK,
>>> + GRF_DPHY_TX1RX1_TURNREQUEST,
>>> + GRF_DPHY_RX1_SRC_SEL,
>>> + /* rk3288 only */
>>> + GRF_CON_DISABLE_ISP,
>>> + GRF_CON_ISP_DPHY_SEL,
>>> + GRF_DSI_CSI_TESTBUS_SEL,
>>> + GRF_DVP_V18SEL,
>>> + /* below is for rk3399 only */
>>> + GRF_DPHY_RX0_CLK_INV_SEL,
>>> + GRF_DPHY_RX1_CLK_INV_SEL,
>>> +};
>>> +
>>> +struct dphy_reg {
>>> + u32 offset;
>>> + u32 mask;
>>> + u32 shift;
>>> +};
>>> +
>>> +#define PHY_REG(_offset, _width, _shift) \
>>> + { .offset = _offset, .mask = BIT(_width) - 1, .shift = _shift, }
>>> +
>>> +static const struct dphy_reg rk3399_grf_dphy_regs[] = {
>>> + [GRF_DPHY_RX0_TURNREQUEST] = PHY_REG(RK3399_GRF_SOC_CON9, 4, 0),
>>> + [GRF_DPHY_RX0_CLK_INV_SEL] = PHY_REG(RK3399_GRF_SOC_CON9, 1, 10),
>>> + [GRF_DPHY_RX1_CLK_INV_SEL] = PHY_REG(RK3399_GRF_SOC_CON9, 1, 11),
>>> + [GRF_DPHY_RX0_ENABLE] = PHY_REG(RK3399_GRF_SOC_CON21, 4, 0),
>>> + [GRF_DPHY_RX0_FORCERXMODE] = PHY_REG(RK3399_GRF_SOC_CON21, 4, 4),
>>> + [GRF_DPHY_RX0_FORCETXSTOPMODE] = PHY_REG(RK3399_GRF_SOC_CON21, 4, 8),
>>> + [GRF_DPHY_RX0_TURNDISABLE] = PHY_REG(RK3399_GRF_SOC_CON21, 4, 12),
>>> + [GRF_DPHY_TX0_FORCERXMODE] = PHY_REG(RK3399_GRF_SOC_CON22, 4, 0),
>>> + [GRF_DPHY_TX0_FORCETXSTOPMODE] = PHY_REG(RK3399_GRF_SOC_CON22, 4, 4),
>>> + [GRF_DPHY_TX0_TURNDISABLE] = PHY_REG(RK3399_GRF_SOC_CON22, 4, 8),
>>> + [GRF_DPHY_TX0_TURNREQUEST] = PHY_REG(RK3399_GRF_SOC_CON22, 4, 12),
>>> + [GRF_DPHY_TX1RX1_ENABLE] = PHY_REG(RK3399_GRF_SOC_CON23, 4, 0),
>>> + [GRF_DPHY_TX1RX1_FORCERXMODE] = PHY_REG(RK3399_GRF_SOC_CON23, 4, 4),
>>> + [GRF_DPHY_TX1RX1_FORCETXSTOPMODE] = PHY_REG(RK3399_GRF_SOC_CON23, 4, 8),
>>> + [GRF_DPHY_TX1RX1_TURNDISABLE] = PHY_REG(RK3399_GRF_SOC_CON23, 4, 12),
>>> + [GRF_DPHY_TX1RX1_TURNREQUEST] = PHY_REG(RK3399_GRF_SOC_CON24, 4, 0),
>>> + [GRF_DPHY_RX1_SRC_SEL] = PHY_REG(RK3399_GRF_SOC_CON24, 1, 4),
>>> + [GRF_DPHY_TX1RX1_BASEDIR] = PHY_REG(RK3399_GRF_SOC_CON24, 1, 5),
>>> + [GRF_DPHY_TX1RX1_ENABLECLK] = PHY_REG(RK3399_GRF_SOC_CON24, 1, 6),
>>> + [GRF_DPHY_TX1RX1_MASTERSLAVEZ] = PHY_REG(RK3399_GRF_SOC_CON24, 1, 7),
>>> + [GRF_DPHY_RX0_TESTDIN] = PHY_REG(RK3399_GRF_SOC_CON25, 8, 0),
>>> + [GRF_DPHY_RX0_TESTEN] = PHY_REG(RK3399_GRF_SOC_CON25, 1, 8),
>>> + [GRF_DPHY_RX0_TESTCLK] = PHY_REG(RK3399_GRF_SOC_CON25, 1, 9),
>>> + [GRF_DPHY_RX0_TESTCLR] = PHY_REG(RK3399_GRF_SOC_CON25, 1, 10),
>>> + [GRF_DPHY_RX0_TESTDOUT] = PHY_REG(RK3399_GRF_SOC_STATUS1, 8, 0),
>>> +};
>>> +
>>> +static const struct dphy_reg rk3288_grf_dphy_regs[] = {
>>> + [GRF_CON_DISABLE_ISP] = PHY_REG(RK3288_GRF_SOC_CON6, 1, 0),
>>> + [GRF_CON_ISP_DPHY_SEL] = PHY_REG(RK3288_GRF_SOC_CON6, 1, 1),
>>> + [GRF_DSI_CSI_TESTBUS_SEL] = PHY_REG(RK3288_GRF_SOC_CON6, 1, 14),
>>> + [GRF_DPHY_TX0_TURNDISABLE] = PHY_REG(RK3288_GRF_SOC_CON8, 4, 0),
>>> + [GRF_DPHY_TX0_FORCERXMODE] = PHY_REG(RK3288_GRF_SOC_CON8, 4, 4),
>>> + [GRF_DPHY_TX0_FORCETXSTOPMODE] = PHY_REG(RK3288_GRF_SOC_CON8, 4, 8),
>>> + [GRF_DPHY_TX1RX1_TURNDISABLE] = PHY_REG(RK3288_GRF_SOC_CON9, 4, 0),
>>> + [GRF_DPHY_TX1RX1_FORCERXMODE] = PHY_REG(RK3288_GRF_SOC_CON9, 4, 4),
>>> + [GRF_DPHY_TX1RX1_FORCETXSTOPMODE] = PHY_REG(RK3288_GRF_SOC_CON9, 4, 8),
>>> + [GRF_DPHY_TX1RX1_ENABLE] = PHY_REG(RK3288_GRF_SOC_CON9, 4, 12),
>>> + [GRF_DPHY_RX0_TURNDISABLE] = PHY_REG(RK3288_GRF_SOC_CON10, 4, 0),
>>> + [GRF_DPHY_RX0_FORCERXMODE] = PHY_REG(RK3288_GRF_SOC_CON10, 4, 4),
>>> + [GRF_DPHY_RX0_FORCETXSTOPMODE] = PHY_REG(RK3288_GRF_SOC_CON10, 4, 8),
>>> + [GRF_DPHY_RX0_ENABLE] = PHY_REG(RK3288_GRF_SOC_CON10, 4, 12),
>>> + [GRF_DPHY_RX0_TESTCLR] = PHY_REG(RK3288_GRF_SOC_CON14, 1, 0),
>>> + [GRF_DPHY_RX0_TESTCLK] = PHY_REG(RK3288_GRF_SOC_CON14, 1, 1),
>>> + [GRF_DPHY_RX0_TESTEN] = PHY_REG(RK3288_GRF_SOC_CON14, 1, 2),
>>> + [GRF_DPHY_RX0_TESTDIN] = PHY_REG(RK3288_GRF_SOC_CON14, 8, 3),
>>> + [GRF_DPHY_TX1RX1_ENABLECLK] = PHY_REG(RK3288_GRF_SOC_CON14, 1, 12),
>>> + [GRF_DPHY_RX1_SRC_SEL] = PHY_REG(RK3288_GRF_SOC_CON14, 1, 13),
>>> + [GRF_DPHY_TX1RX1_MASTERSLAVEZ] = PHY_REG(RK3288_GRF_SOC_CON14, 1, 14),
>>> + [GRF_DPHY_TX1RX1_BASEDIR] = PHY_REG(RK3288_GRF_SOC_CON14, 1, 15),
>>> + [GRF_DPHY_RX0_TURNREQUEST] = PHY_REG(RK3288_GRF_SOC_CON15, 4, 0),
>>> + [GRF_DPHY_TX1RX1_TURNREQUEST] = PHY_REG(RK3288_GRF_SOC_CON15, 4, 4),
>>> + [GRF_DPHY_TX0_TURNREQUEST] = PHY_REG(RK3288_GRF_SOC_CON15, 3, 8),
>>> + [GRF_DVP_V18SEL] = PHY_REG(RK3288_GRF_IO_VSEL, 1, 1),
>>> + [GRF_DPHY_RX0_TESTDOUT] = PHY_REG(RK3288_GRF_SOC_STATUS21, 8, 0),
>>> +};
>>> +
>>> +struct hsfreq_range {
>>> + u32 range_h;
>>> + u8 cfg_bit;
>>> +};
>>> +
>>> +struct mipidphy_priv;
>>> +
>>> +struct dphy_drv_data {
>>> + const char * const *clks;
>>> + int num_clks;
>>> + const struct hsfreq_range *hsfreq_ranges;
>>> + int num_hsfreq_ranges;
>>> + const struct dphy_reg *regs;
>>> +};
>>> +
>>> +struct sensor_async_subdev {
>>> + struct v4l2_async_subdev asd;
>>> + struct v4l2_mbus_config mbus;
>>> + int lanes;
>>> +};
>>> +
>>> +#define MAX_DPHY_CLK 8
>>> +#define MAX_DPHY_SENSORS 2
>>> +
>>> +struct mipidphy_sensor {
>>> + struct v4l2_subdev *sd;
>>> + struct v4l2_mbus_config mbus;
>>> + int lanes;
>>> +};
>>> +
>>> +struct mipidphy_priv {
>>> + struct device *dev;
>>> + struct regmap *regmap_grf;
>>> + const struct dphy_reg *grf_regs;
>>> + struct clk *clks[MAX_DPHY_CLK];
>>> + const struct dphy_drv_data *drv_data;
>>> + u64 data_rate_mbps;
>>> + struct v4l2_async_notifier notifier;
>>> + struct v4l2_subdev sd;
>>> + struct media_pad pads[MIPI_DPHY_SY_PADS_NUM];
>>> + struct mipidphy_sensor sensors[MAX_DPHY_SENSORS];
>>> + int num_sensors;
>>> + bool is_streaming;
>>> + void __iomem *txrx_base_addr;
>>> + int (*stream_on)(struct mipidphy_priv *priv, struct v4l2_subdev *sd);
>>> +};
>>> +
>>> +static inline struct mipidphy_priv *to_dphy_priv(struct v4l2_subdev
>>> *subdev) +{
>>> + return container_of(subdev, struct mipidphy_priv, sd);
>>> +}
>>> +
>>> +static inline void write_grf_reg(struct mipidphy_priv *priv,
>>> + int index, u8 value)
>>> +{
>>> + const struct dphy_reg *reg = &priv->grf_regs[index];
>>> + unsigned int val = HIWORD_UPDATE(value, reg->mask, reg->shift);
>>> +
>>> + WARN_ON(!reg->offset);
>>> + regmap_write(priv->regmap_grf, reg->offset, val);
>>> +}
>>> +
>>> +static void mipidphy0_wr_reg(struct mipidphy_priv *priv,
>>> + u8 test_code, u8 test_data)
>>> +{
>>> + /*
>>> + * With the falling edge on TESTCLK, the TESTDIN[7:0] signal content
>>> + * is latched internally as the current test code. Test data is
>>> + * programmed internally by rising edge on TESTCLK.
>>> + */
>>> + write_grf_reg(priv, GRF_DPHY_RX0_TESTCLK, 1);
>>> + write_grf_reg(priv, GRF_DPHY_RX0_TESTDIN, test_code);
>>> + write_grf_reg(priv, GRF_DPHY_RX0_TESTEN, 1);
>>> + write_grf_reg(priv, GRF_DPHY_RX0_TESTCLK, 0);
>>> + write_grf_reg(priv, GRF_DPHY_RX0_TESTEN, 0);
>>> + write_grf_reg(priv, GRF_DPHY_RX0_TESTDIN, test_data);
>>> + write_grf_reg(priv, GRF_DPHY_RX0_TESTCLK, 1);
>>> +}
>>> +
>>> +static void mipidphy1_wr_reg(struct mipidphy_priv *priv, unsigned char
>>> addr, + unsigned char data)
>>> +{
>>> + /*
>>> + * TESTEN =1,TESTDIN=addr
>>> + * TESTCLK=0
>>> + * TESTEN =0,TESTDIN=data
>>> + * TESTCLK=1
>>> + */
>>> + writel((PHY_TESTEN_ADDR | addr),
>>> + priv->txrx_base_addr + CSIHOST_PHY_TEST_CTRL1);
>>> + writel(0x00, priv->txrx_base_addr + CSIHOST_PHY_TEST_CTRL0);
>>> + writel((PHY_TESTEN_DATA | data),
>>> + priv->txrx_base_addr + CSIHOST_PHY_TEST_CTRL1);
>>> + writel(PHY_TESTCLK, priv->txrx_base_addr + CSIHOST_PHY_TEST_CTRL0);
>>> +}
>>> +
>>> +static struct v4l2_subdev *get_remote_sensor(struct v4l2_subdev *sd)
>>> +{
>>> + struct media_pad *local, *remote;
>>> + struct media_entity *sensor_me;
>>> +
>>> + local = &sd->entity.pads[MIPI_DPHY_SY_PAD_SINK];
>>> + remote = media_entity_remote_pad(local);
>>> + if (!remote) {
>>> + v4l2_warn(sd, "No link between dphy and sensor\n");
>>> + return NULL;
>>> + }
>>> +
>>> + sensor_me = media_entity_remote_pad(local)->entity;
>>> + return media_entity_to_v4l2_subdev(sensor_me);
>>> +}
>>> +
>>> +static struct mipidphy_sensor *sd_to_sensor(struct mipidphy_priv *priv,
>>> + struct v4l2_subdev *sd)
>>> +{
>>> + int i;
>>> +
>>> + for (i = 0; i < priv->num_sensors; ++i)
>>> + if (priv->sensors[i].sd == sd)
>>> + return &priv->sensors[i];
>>> +
>>> + return NULL;
>>> +}
>>> +
>>> +static int mipidphy_get_sensor_data_rate(struct v4l2_subdev *sd)
>>> +{
>>> + struct mipidphy_priv *priv = to_dphy_priv(sd);
>>> + struct v4l2_subdev *sensor_sd = get_remote_sensor(sd);
>>> + struct v4l2_ctrl *link_freq;
>>> + struct v4l2_querymenu qm = { .id = V4L2_CID_LINK_FREQ, };
>>> + int ret;
>>> +
>>> + link_freq = v4l2_ctrl_find(sensor_sd->ctrl_handler, V4L2_CID_LINK_FREQ);
>>> + if (!link_freq) {
>>> + v4l2_warn(sd, "No pixel rate control in subdev\n");
>>> + return -EPIPE;
>>> + }
>>> +
>>> + qm.index = v4l2_ctrl_g_ctrl(link_freq);
>>> + ret = v4l2_querymenu(sensor_sd->ctrl_handler, &qm);
>>> + if (ret < 0) {
>>> + v4l2_err(sd, "Failed to get menu item\n");
>>> + return ret;
>>> + }
>>> +
>>> + if (!qm.value) {
>>> + v4l2_err(sd, "Invalid link_freq\n");
>>> + return -EINVAL;
>>> + }
>>> + priv->data_rate_mbps = qm.value * 2;
>>> + do_div(priv->data_rate_mbps, 1000 * 1000);
>>> +
>>> + return 0;
>>> +}
>>> +
>>> +static int mipidphy_s_stream_start(struct v4l2_subdev *sd)
>>> +{
>>> + struct mipidphy_priv *priv = to_dphy_priv(sd);
>>> + int ret = 0;
>>> +
>>> + if (priv->is_streaming)
>>> + return 0;
>>> +
>>> + ret = mipidphy_get_sensor_data_rate(sd);
>>> + if (ret < 0)
>>> + return ret;
>>> +
>>> + priv->stream_on(priv, sd);
>>> +
>>> + priv->is_streaming = true;
>>> +
>>> + return 0;
>>> +}
>>> +
>>> +static int mipidphy_s_stream_stop(struct v4l2_subdev *sd)
>>> +{
>>> + struct mipidphy_priv *priv = to_dphy_priv(sd);
>>> +
>>> + priv->is_streaming = false;
>>> +
>>> + return 0;
>>> +}
>>> +
>>> +static int mipidphy_s_stream(struct v4l2_subdev *sd, int on)
>>> +{
>>> + if (on)
>>> + return mipidphy_s_stream_start(sd);
>>> + else
>>> + return mipidphy_s_stream_stop(sd);
>>> +}
>>> +
>>> +static int mipidphy_g_mbus_config(struct v4l2_subdev *sd,
>>> + struct v4l2_mbus_config *config)
>>> +{
>>> + struct mipidphy_priv *priv = to_dphy_priv(sd);
>>> + struct v4l2_subdev *sensor_sd = get_remote_sensor(sd);
>>> + struct mipidphy_sensor *sensor = sd_to_sensor(priv, sensor_sd);
>>> +
>>> + *config = sensor->mbus;
>>> +
>>> + return 0;
>>> +}
>>> +
>>> +static int mipidphy_s_power(struct v4l2_subdev *sd, int on)
>>> +{
>>> + struct mipidphy_priv *priv = to_dphy_priv(sd);
>>> +
>>> + if (on)
>>> + return pm_runtime_get_sync(priv->dev);
>>> + else
>>> + return pm_runtime_put(priv->dev);
>>> +}
>>> +
>>> +static int mipidphy_runtime_suspend(struct device *dev)
>>> +{
>>> + struct media_entity *me = dev_get_drvdata(dev);
>>> + struct v4l2_subdev *sd = media_entity_to_v4l2_subdev(me);
>>> + struct mipidphy_priv *priv = to_dphy_priv(sd);
>>> + int i, num_clks;
>>> +
>>> + num_clks = priv->drv_data->num_clks;
>>> + for (i = num_clks - 1; i >= 0; i--)
>>> + clk_disable_unprepare(priv->clks[i]);
>>> +
>>> + return 0;
>>> +}
>>> +
>>> +static int mipidphy_runtime_resume(struct device *dev)
>>> +{
>>> + struct media_entity *me = dev_get_drvdata(dev);
>>> + struct v4l2_subdev *sd = media_entity_to_v4l2_subdev(me);
>>> + struct mipidphy_priv *priv = to_dphy_priv(sd);
>>> + int i, num_clks, ret;
>>> +
>>> + num_clks = priv->drv_data->num_clks;
>>> + for (i = 0; i < num_clks; i++) {
>>> + ret = clk_prepare_enable(priv->clks[i]);
>>> + if (ret < 0)
>>> + goto err;
>>> + }
>>> +
>>> + return 0;
>>> +err:
>>> + while (--i >= 0)
>>> + clk_disable_unprepare(priv->clks[i]);
>>> + return ret;
>>> +}
>>> +
>>> +/* dphy accepts all fmt/size from sensor */
>>> +static int mipidphy_get_set_fmt(struct v4l2_subdev *sd,
>>> + struct v4l2_subdev_pad_config *cfg,
>>> + struct v4l2_subdev_format *fmt)
>>> +{
>>> + struct v4l2_subdev *sensor = get_remote_sensor(sd);
>>> +
>>> + /*
>>> + * Do not allow format changes and just relay whatever
>>> + * set currently in the sensor.
>>> + */
>>> + return v4l2_subdev_call(sensor, pad, get_fmt, NULL, fmt);
>>> +}
>>> +
>>> +static const struct v4l2_subdev_pad_ops mipidphy_subdev_pad_ops = {
>>> + .set_fmt = mipidphy_get_set_fmt,
>>> + .get_fmt = mipidphy_get_set_fmt,
>>> +};
>>> +
>>> +static const struct v4l2_subdev_core_ops mipidphy_core_ops = {
>>> + .s_power = mipidphy_s_power,
>>> +};
>>> +
>>> +static const struct v4l2_subdev_video_ops mipidphy_video_ops = {
>>> + .g_mbus_config = mipidphy_g_mbus_config,
>>> + .s_stream = mipidphy_s_stream,
>>> +};
>>> +
>>> +static const struct v4l2_subdev_ops mipidphy_subdev_ops = {
>>> + .core = &mipidphy_core_ops,
>>> + .video = &mipidphy_video_ops,
>>> + .pad = &mipidphy_subdev_pad_ops,
>>> +};
>>> +
>>> +/* These tables must be sorted by .range_h ascending. */
>>> +static const struct hsfreq_range rk3288_mipidphy_hsfreq_ranges[] = {
>>> + { 89, 0x00}, { 99, 0x10}, { 109, 0x20}, { 129, 0x01},
>>> + { 139, 0x11}, { 149, 0x21}, { 169, 0x02}, { 179, 0x12},
>>> + { 199, 0x22}, { 219, 0x03}, { 239, 0x13}, { 249, 0x23},
>>> + { 269, 0x04}, { 299, 0x14}, { 329, 0x05}, { 359, 0x15},
>>> + { 399, 0x25}, { 449, 0x06}, { 499, 0x16}, { 549, 0x07},
>>> + { 599, 0x17}, { 649, 0x08}, { 699, 0x18}, { 749, 0x09},
>>> + { 799, 0x19}, { 849, 0x29}, { 899, 0x39}, { 949, 0x0a},
>>> + { 999, 0x1a}
>>> +};
>>> +
>>> +static const struct hsfreq_range rk3399_mipidphy_hsfreq_ranges[] = {
>>> + { 89, 0x00}, { 99, 0x10}, { 109, 0x20}, { 129, 0x01},
>>> + { 139, 0x11}, { 149, 0x21}, { 169, 0x02}, { 179, 0x12},
>>> + { 199, 0x22}, { 219, 0x03}, { 239, 0x13}, { 249, 0x23},
>>> + { 269, 0x04}, { 299, 0x14}, { 329, 0x05}, { 359, 0x15},
>>> + { 399, 0x25}, { 449, 0x06}, { 499, 0x16}, { 549, 0x07},
>>> + { 599, 0x17}, { 649, 0x08}, { 699, 0x18}, { 749, 0x09},
>>> + { 799, 0x19}, { 849, 0x29}, { 899, 0x39}, { 949, 0x0a},
>>> + { 999, 0x1a}, {1049, 0x2a}, {1099, 0x3a}, {1149, 0x0b},
>>> + {1199, 0x1b}, {1249, 0x2b}, {1299, 0x3b}, {1349, 0x0c},
>>> + {1399, 0x1c}, {1449, 0x2c}, {1500, 0x3c}
>>> +};
>>> +
>>> +static const char * const rk3399_mipidphy_clks[] = {
>>> + "dphy-ref",
>>> + "dphy-cfg",
>>> + "grf",
>>> +};
>>> +
>>> +static const char * const rk3288_mipidphy_clks[] = {
>>> + "dphy-ref",
>>> + "pclk",
>>> +};
>>> +
>>> +static int mipidphy_rx_stream_on(struct mipidphy_priv *priv,
>>> + struct v4l2_subdev *sd)
>>> +{
>>> + struct v4l2_subdev *sensor_sd = get_remote_sensor(sd);
>>> + struct mipidphy_sensor *sensor = sd_to_sensor(priv, sensor_sd);
>>> + const struct dphy_drv_data *drv_data = priv->drv_data;
>>> + const struct hsfreq_range *hsfreq_ranges = drv_data->hsfreq_ranges;
>>> + int num_hsfreq_ranges = drv_data->num_hsfreq_ranges;
>>> + int i, hsfreq = 0;
>>> +
>>> + for (i = 0; i < num_hsfreq_ranges; i++) {
>>> + if (hsfreq_ranges[i].range_h >= priv->data_rate_mbps) {
>>> + hsfreq = hsfreq_ranges[i].cfg_bit;
>>> + break;
>>> + }
>>> + }
>>> + write_grf_reg(priv, GRF_CON_ISP_DPHY_SEL, 0);
>>> + write_grf_reg(priv, GRF_DPHY_RX0_FORCERXMODE, 0);
>>> + write_grf_reg(priv, GRF_DPHY_RX0_FORCETXSTOPMODE, 0);
>>> + /* Disable lan turn around, which is ignored in receive mode */
>>> + write_grf_reg(priv, GRF_DPHY_RX0_TURNREQUEST, 0);
>>> + write_grf_reg(priv, GRF_DPHY_RX0_TURNDISABLE, 0xf);
>>> +
>>> + write_grf_reg(priv, GRF_DPHY_RX0_ENABLE, GENMASK(sensor->lanes - 1, 0));
>>> +
>>> + /* dphy start */
>>> + write_grf_reg(priv, GRF_DPHY_RX0_TESTCLK, 1);
>>> + write_grf_reg(priv, GRF_DPHY_RX0_TESTCLR, 1);
>>> + usleep_range(100, 150);
>>> + write_grf_reg(priv, GRF_DPHY_RX0_TESTCLR, 0);
>>> + usleep_range(100, 150);
>>> +
>>> + /* set clock lane */
>>> + /* HS hsfreq_range & lane 0 settle bypass */
>>> + mipidphy0_wr_reg(priv, CLOCK_LANE_HS_RX_CONTROL, 0);
>>> + /* HS RX Control of lane0 */
>>> + mipidphy0_wr_reg(priv, LANE0_HS_RX_CONTROL, hsfreq << 1);
>>> + /* HS RX Control of lane1 */
>>> + mipidphy0_wr_reg(priv, LANE1_HS_RX_CONTROL, 0);
>>> + /* HS RX Control of lane2 */
>>> + mipidphy0_wr_reg(priv, LANE2_HS_RX_CONTROL, 0);
>>> + /* HS RX Control of lane3 */
>>> + mipidphy0_wr_reg(priv, LANE3_HS_RX_CONTROL, 0);
>>> + /* HS RX Data Lanes Settle State Time Control */
>>> + mipidphy0_wr_reg(priv, HS_RX_DATA_LANES_THS_SETTLE_CONTROL,
>>> + THS_SETTLE_COUNTER_THRESHOLD);
>>> +
>>> + /* Normal operation */
>>> + mipidphy0_wr_reg(priv, 0x0, 0);
>>> +
>>> + return 0;
>>> +}
>>> +
>>> +static int mipidphy_txrx_stream_on(struct mipidphy_priv *priv,
>>> + struct v4l2_subdev *sd)
>>> +{
>>> + struct v4l2_subdev *sensor_sd = get_remote_sensor(sd);
>>> + struct mipidphy_sensor *sensor = sd_to_sensor(priv, sensor_sd);
>>> + const struct dphy_drv_data *drv_data = priv->drv_data;
>>> + const struct hsfreq_range *hsfreq_ranges = drv_data->hsfreq_ranges;
>>> + int num_hsfreq_ranges = drv_data->num_hsfreq_ranges;
>>> + int i, hsfreq = 0;
>>> +
>>> + for (i = 0; i < num_hsfreq_ranges; i++) {
>>> + if (hsfreq_ranges[i].range_h >= priv->data_rate_mbps) {
>>> + hsfreq = hsfreq_ranges[i].cfg_bit;
>>> + break;
>>> + }
>>> + }
>>> + write_grf_reg(priv, GRF_CON_ISP_DPHY_SEL, 1);
>>> + write_grf_reg(priv, GRF_DSI_CSI_TESTBUS_SEL, 1);
>>> + write_grf_reg(priv, GRF_DPHY_RX1_SRC_SEL, 1);
>>> + write_grf_reg(priv, GRF_DPHY_TX1RX1_MASTERSLAVEZ, 0);
>>> + write_grf_reg(priv, GRF_DPHY_TX1RX1_BASEDIR, 1);
>>> + /* Disable lan turn around, which is ignored in receive mode */
>>> + write_grf_reg(priv, GRF_DPHY_TX1RX1_FORCERXMODE, 0);
>>> + write_grf_reg(priv, GRF_DPHY_TX1RX1_FORCETXSTOPMODE, 0);
>>> + write_grf_reg(priv, GRF_DPHY_TX1RX1_TURNREQUEST, 0);
>>> + write_grf_reg(priv, GRF_DPHY_TX1RX1_TURNDISABLE, 0xf);
>>> + write_grf_reg(priv, GRF_DPHY_TX1RX1_ENABLE,
>>> + GENMASK(sensor->lanes - 1, 0));
>>> + /* dphy start */
>>> + writel(0, priv->txrx_base_addr + CSIHOST_PHY_SHUTDOWNZ);
>>> + writel(0, priv->txrx_base_addr + CSIHOST_DPHY_RSTZ);
>>> + writel(PHY_TESTCLK, priv->txrx_base_addr + CSIHOST_PHY_TEST_CTRL0);
>>> + writel(PHY_TESTCLR, priv->txrx_base_addr + CSIHOST_PHY_TEST_CTRL0);
>>> + usleep_range(100, 150);
>>> + writel(PHY_TESTCLK, priv->txrx_base_addr + CSIHOST_PHY_TEST_CTRL0);
>>> + usleep_range(100, 150);
>>> +
>>> + /* set clock lane */
>>> + mipidphy1_wr_reg(priv, CLOCK_LANE_HS_RX_CONTROL, 0);
>>> + mipidphy1_wr_reg(priv, LANE0_HS_RX_CONTROL, hsfreq << 1);
>>> + mipidphy1_wr_reg(priv, LANE1_HS_RX_CONTROL, 0);
>>> + mipidphy1_wr_reg(priv, LANE2_HS_RX_CONTROL, 0);
>>> + mipidphy1_wr_reg(priv, LANE3_HS_RX_CONTROL, 0);
>>> + /* HS RX Data Lanes Settle State Time Control */
>>> + mipidphy1_wr_reg(priv, HS_RX_DATA_LANES_THS_SETTLE_CONTROL,
>>> + THS_SETTLE_COUNTER_THRESHOLD);
>>> +
>>> + /* Normal operation */
>>> + mipidphy1_wr_reg(priv, 0x0, 0);
>>> +
>>> + return 0;
>>> +}
>>> +
>>> +static const struct dphy_drv_data rk3288_mipidphy_drv_data = {
>>> + .clks = rk3288_mipidphy_clks,
>>> + .num_clks = ARRAY_SIZE(rk3288_mipidphy_clks),
>>> + .hsfreq_ranges = rk3288_mipidphy_hsfreq_ranges,
>>> + .num_hsfreq_ranges = ARRAY_SIZE(rk3288_mipidphy_hsfreq_ranges),
>>> + .regs = rk3288_grf_dphy_regs,
>>> +};
>>> +
>>> +static const struct dphy_drv_data rk3399_mipidphy_drv_data = {
>>> + .clks = rk3399_mipidphy_clks,
>>> + .num_clks = ARRAY_SIZE(rk3399_mipidphy_clks),
>>> + .hsfreq_ranges = rk3399_mipidphy_hsfreq_ranges,
>>> + .num_hsfreq_ranges = ARRAY_SIZE(rk3399_mipidphy_hsfreq_ranges),
>>> + .regs = rk3399_grf_dphy_regs,
>>> +};
>>> +
>>> +static const struct of_device_id rockchip_mipidphy_match_id[] = {
>>> + {
>>> + .compatible = "rockchip,rk3399-mipi-dphy",
>>> + .data = &rk3399_mipidphy_drv_data,
>>> + },
>>> + {
>>> + .compatible = "rockchip,rk3288-mipi-dphy",
>>> + .data = &rk3288_mipidphy_drv_data,
>>> + },
>>> + {}
>>> +};
>>> +MODULE_DEVICE_TABLE(of, rockchip_mipidphy_match_id);
>>> +
>>> +/* The .bound() notifier callback when a match is found */
>>> +static int
>>> +rockchip_mipidphy_notifier_bound(struct v4l2_async_notifier *notifier,
>>> + struct v4l2_subdev *sd,
>>> + struct v4l2_async_subdev *asd)
>>> +{
>>> + struct mipidphy_priv *priv = container_of(notifier,
>>> + struct mipidphy_priv,
>>> + notifier);
>>> + struct sensor_async_subdev *s_asd = container_of(asd,
>>> + struct sensor_async_subdev, asd);
>>> + struct mipidphy_sensor *sensor;
>>> + unsigned int pad, ret;
>>> +
>>> + if (priv->num_sensors == ARRAY_SIZE(priv->sensors))
>>> + return -EBUSY;
>>> +
>>> + sensor = &priv->sensors[priv->num_sensors++];
>>> + sensor->lanes = s_asd->lanes;
>>> + sensor->mbus = s_asd->mbus;
>>> + sensor->sd = sd;
>>> +
>>> + for (pad = 0; pad < sensor->sd->entity.num_pads; pad++)
>>> + if (sensor->sd->entity.pads[pad].flags
>>> + & MEDIA_PAD_FL_SOURCE)
>>> + break;
>>> +
>>> + if (pad == sensor->sd->entity.num_pads) {
>>> + dev_err(priv->dev,
>>> + "failed to find src pad for %s\n",
>>> + sensor->sd->name);
>>> +
>>> + return -ENXIO;
>>> + }
>>> +
>>> + ret = media_create_pad_link(
>>> + &sensor->sd->entity, pad,
>>> + &priv->sd.entity, MIPI_DPHY_SY_PAD_SINK,
>>> + priv->num_sensors != 1 ? 0 : MEDIA_LNK_FL_ENABLED);
>>> + if (ret) {
>>> + dev_err(priv->dev,
>>> + "failed to create link for %s\n",
>>> + sensor->sd->name);
>>> + return ret;
>>> + }
>>> +
>>> + return 0;
>>> +}
>>> +
>>> +/* The .unbind callback */
>>> +static void
>>> +rockchip_mipidphy_notifier_unbind(struct v4l2_async_notifier *notifier,
>>> + struct v4l2_subdev *sd,
>>> + struct v4l2_async_subdev *asd)
>>> +{
>>> + struct mipidphy_priv *priv = container_of(notifier,
>>> + struct mipidphy_priv,
>>> + notifier);
>>> + struct mipidphy_sensor *sensor = sd_to_sensor(priv, sd);
>>> +
>>> + sensor->sd = NULL;
>>> +}
>>> +
>>> +static const struct
>>> +v4l2_async_notifier_operations rockchip_mipidphy_async_ops = {
>>> + .bound = rockchip_mipidphy_notifier_bound,
>>> + .unbind = rockchip_mipidphy_notifier_unbind,
>>> +};
>>> +
>>> +static int rockchip_mipidphy_fwnode_parse(struct device *dev,
>>> + struct v4l2_fwnode_endpoint *vep,
>>> + struct v4l2_async_subdev *asd)
>>> +{
>>> + struct sensor_async_subdev *s_asd =
>>> + container_of(asd, struct sensor_async_subdev, asd);
>>> + struct v4l2_mbus_config *config = &s_asd->mbus;
>>> +
>>> + if (vep->bus_type != V4L2_MBUS_CSI2) {
>>> + dev_err(dev, "Only CSI2 bus type is currently supported\n");
>>> + return -EINVAL;
>>> + }
>>> +
>>> + if (vep->base.port != 0) {
>>> + dev_err(dev, "The PHY has only port 0\n");
>>> + return -EINVAL;
>>> + }
>>> +
>>> + config->type = V4L2_MBUS_CSI2;
>>> + config->flags = vep->bus.mipi_csi2.flags;
>>> + s_asd->lanes = vep->bus.mipi_csi2.num_data_lanes;
>>> +
>>> + switch (vep->bus.mipi_csi2.num_data_lanes) {
>>> + case 1:
>>> + config->flags |= V4L2_MBUS_CSI2_1_LANE;
>>> + break;
>>> + case 2:
>>> + config->flags |= V4L2_MBUS_CSI2_2_LANE;
>>> + break;
>>> + case 3:
>>> + config->flags |= V4L2_MBUS_CSI2_3_LANE;
>>> + break;
>>> + case 4:
>>> + config->flags |= V4L2_MBUS_CSI2_4_LANE;
>>> + break;
>>> + default:
>>> + return -EINVAL;
>>> + }
>>> +
>>> + return 0;
>>> +}
>>> +
>>> +static int rockchip_mipidphy_media_init(struct mipidphy_priv *priv)
>>> +{
>>> + int ret;
>>> +
>>> + priv->pads[MIPI_DPHY_SY_PAD_SOURCE].flags =
>>> + MEDIA_PAD_FL_SOURCE | MEDIA_PAD_FL_MUST_CONNECT;
>>> + priv->pads[MIPI_DPHY_SY_PAD_SINK].flags =
>>> + MEDIA_PAD_FL_SINK | MEDIA_PAD_FL_MUST_CONNECT;
>>> +
>>> + ret = media_entity_pads_init(&priv->sd.entity,
>>> + MIPI_DPHY_SY_PADS_NUM, priv->pads);
>>> + if (ret < 0)
>>> + return ret;
>>> +
>>> + ret = v4l2_async_notifier_parse_fwnode_endpoints_by_port(
>>> + priv->dev, &priv->notifier,
>>> + sizeof(struct sensor_async_subdev), 0,
>>> + rockchip_mipidphy_fwnode_parse);
>>> + if (ret < 0)
>>> + return ret;
>>> +
>>> + if (!priv->notifier.num_subdevs)
>>> + return -ENODEV; /* no endpoint */
>>> +
>>> + priv->sd.subdev_notifier = &priv->notifier;
>>> + priv->notifier.ops = &rockchip_mipidphy_async_ops;
>>> + ret = v4l2_async_subdev_notifier_register(&priv->sd, &priv->notifier);
>>> + if (ret) {
>>> + dev_err(priv->dev,
>>> + "failed to register async notifier : %d\n", ret);
>>> + v4l2_async_notifier_cleanup(&priv->notifier);
>>> + return ret;
>>> + }
>>> +
>>> + return v4l2_async_register_subdev(&priv->sd);
>>> +}
>>> +
>>> +static int rockchip_mipidphy_probe(struct platform_device *pdev)
>>> +{
>>> + struct device *dev = &pdev->dev;
>>> + struct v4l2_subdev *sd;
>>> + struct mipidphy_priv *priv;
>>> + struct regmap *grf;
>>> + struct resource *res;
>>> + const struct of_device_id *of_id;
>>> + const struct dphy_drv_data *drv_data;
>>> + int i, ret;
>>> +
>>> + priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL);
>>> + if (!priv)
>>> + return -ENOMEM;
>>> + priv->dev = dev;
>>> +
>>> + of_id = of_match_device(rockchip_mipidphy_match_id, dev);
>>> + if (!of_id)
>>> + return -EINVAL;
>>> +
>>> + grf = syscon_node_to_regmap(dev->parent->of_node);
>>> + if (IS_ERR(grf)) {
>>> + grf = syscon_regmap_lookup_by_phandle(dev->of_node,
>>> + "rockchip,grf");
>>> + if (IS_ERR(grf)) {
>>> + dev_err(dev, "Can't find GRF syscon\n");
>>> + return -ENODEV;
>>> + }
>>> + }
>>> + priv->regmap_grf = grf;
>>> +
>>> + drv_data = of_id->data;
>>> + for (i = 0; i < drv_data->num_clks; i++) {
>>> + priv->clks[i] = devm_clk_get(dev, drv_data->clks[i]);
>>> +
>>> + if (IS_ERR(priv->clks[i])) {
>>> + dev_err(dev, "Failed to get %s\n", drv_data->clks[i]);
>>> + return PTR_ERR(priv->clks[i]);
>>> + }
>>> + }
>>> +
>>> + priv->grf_regs = drv_data->regs;
>>> + priv->drv_data = drv_data;
>>> + priv->stream_on = mipidphy_txrx_stream_on;
>>> + priv->txrx_base_addr = NULL;
>>> + res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
>>> + priv->txrx_base_addr = devm_ioremap_resource(dev, res);
>>> + if (IS_ERR(priv->txrx_base_addr))
>>> + priv->stream_on = mipidphy_rx_stream_on;
>>> +
>>> + sd = &priv->sd;
>>> + v4l2_subdev_init(sd, &mipidphy_subdev_ops);
>>> + sd->flags |= V4L2_SUBDEV_FL_HAS_DEVNODE;
>>> + snprintf(sd->name, sizeof(sd->name), "rockchip-sy-mipi-dphy");
>>> + sd->dev = dev;
>>> +
>>> + platform_set_drvdata(pdev, &sd->entity);
>>> +
>>> + ret = rockchip_mipidphy_media_init(priv);
>>> + if (ret < 0)
>>> + return ret;
>>> +
>>> + pm_runtime_enable(&pdev->dev);
>>> +
>>> + return 0;
>>> +}
>>> +
>>> +static int rockchip_mipidphy_remove(struct platform_device *pdev)
>>> +{
>>> + struct media_entity *me = platform_get_drvdata(pdev);
>>> + struct v4l2_subdev *sd = media_entity_to_v4l2_subdev(me);
>>> +
>>> + media_entity_cleanup(&sd->entity);
>>> +
>>> + pm_runtime_disable(&pdev->dev);
>>> +
>>> + return 0;
>>> +}
>>> +
>>> +static const struct dev_pm_ops rockchip_mipidphy_pm_ops = {
>>> + SET_RUNTIME_PM_OPS(mipidphy_runtime_suspend,
>>> + mipidphy_runtime_resume, NULL)
>>> +};
>>> +
>>> +static struct platform_driver rockchip_isp_mipidphy_driver = {
>>> + .probe = rockchip_mipidphy_probe,
>>> + .remove = rockchip_mipidphy_remove,
>>> + .driver = {
>>> + .name = "rockchip-sy-mipi-dphy",
>>> + .pm = &rockchip_mipidphy_pm_ops,
>>> + .of_match_table = rockchip_mipidphy_match_id,
>>> + },
>>> +};
>>> +
>>> +module_platform_driver(rockchip_isp_mipidphy_driver);
>>> +MODULE_AUTHOR("Rockchip Camera/ISP team");
>>> +MODULE_DESCRIPTION("Rockchip MIPI DPHY driver");
>>> +MODULE_LICENSE("Dual BSD/GPL");
>>> diff --git a/drivers/media/platform/rockchip/isp1/mipi_dphy_sy.h
>>> b/drivers/media/platform/rockchip/isp1/mipi_dphy_sy.h new file mode 100644
>>> index 000000000000..c558791064a2
>>> --- /dev/null
>>> +++ b/drivers/media/platform/rockchip/isp1/mipi_dphy_sy.h
>>> @@ -0,0 +1,15 @@
>>> +// SPDX-License-Identifier: (GPL-2.0+ OR MIT)
>>> +/*
>>> + * Rockchip MIPI Synopsys DPHY driver
>>> + *
>>> + * Copyright (C) 2017 Fuzhou Rockchip Electronics Co., Ltd.
>>> + */
>>> +
>>> +#ifndef __MIPI_DPHY_SY_H__
>>> +#define __MIPI_DPHY_SY_H__
>>> +
>>> +#include <media/v4l2-subdev.h>
>>> +
>>> +void rkisp1_set_mipi_dphy_sy_lanes(struct v4l2_subdev *dphy, int lanes);
>>> +
>>> +#endif /* __RKISP1_MIPI_DPHY_SY_H__ */
>>
>>
>> --
>> Regards,
>>
>> Laurent Pinchart
>>
>>
>>
^ permalink raw reply
* [PATCH 1/5] dt-bindings: pinctrl: document the STMFX pinctrl bindings
From: Amelie DELAUNAY @ 2018-05-16 15:01 UTC (permalink / raw)
To: linux-arm-kernel
In-Reply-To: <CACRpkda2VGo0FOdR09tKiTpg66NgoWjubaHp_J3372y=a772Zg@mail.gmail.com>
On 05/16/2018 04:20 PM, Linus Walleij wrote:
> On Wed, May 9, 2018 at 9:56 AM, Amelie DELAUNAY <amelie.delaunay@st.com> wrote:
>
>> Indeed, stmfx has other functions than GPIO. But, after comments done
>> here: [1] and there: [2], it has been decided to move MFD parent/GPIO
>> child drivers into a single PINCTRL/GPIO driver because of the following
>> reasons:
>> - Other stmfx functions (IDD measurement and TouchScreen controller) are
>> not used on any of the boards using an stmfx and supported by Linux, so
>> no way to test these functions, and no need to maintain them while they
>> are not being used.
>> - But, in the case a new board will use more than GPIO function on
>> stmfx, the actual implementation allow to easily extract common init
>> part of stmfx and put it in an MFD driver.
>>
>> So I could remove gpio sub-node and put its contents in stmfx node and
>> keep single PINCTRL/GPIO driver for the time being.
>> Please advise,
>
> I would normally advice to use the right modeling from the start, create
> the MFD driver and spawn the devices from there. It is confusing
> if the layout of the driver(s) doesn't really match the layout of the
> hardware.
>
> I understand that it is a pain to write new MFD drivers to get your
> things going and it would be "nice to get this working really quick
> now" but in my experience it is better to do it right from the start.
>
Hi Linus,
Thanks for your advice. I understand the point.
So, the right modeling would be to:
- create an MFD driver with the common init part of stmfx
- remove all common init part of stmfx-pinctrl driver and keep only all
gpio/pinctrl functions.
I will not develop the other stmfx functions (IDD measurement driver and
TouchScreen controller driver) because, as explained ealier, they are
not used on any of the boards using an stmfx and supported by Linux, so
no way to test these functions, and no need to maintain them while they
are not being used.
Lee, are you OK with that ?
Regards,
Amelie
^ permalink raw reply
* [PATCH 14/14] mm: turn on vm_fault_t type checking
From: Darrick J. Wong @ 2018-05-16 15:08 UTC (permalink / raw)
To: linux-arm-kernel
In-Reply-To: <20180516054348.15950-15-hch@lst.de>
On Wed, May 16, 2018 at 07:43:48AM +0200, Christoph Hellwig wrote:
> Switch vm_fault_t to point to an unsigned int with __b?twise annotations.
> This both catches any old ->fault or ->page_mkwrite instance with plain
> compiler type checking, as well as finding more intricate problems with
> sparse.
>
> Signed-off-by: Christoph Hellwig <hch@lst.de>
> ---
<ULTRASNIP>
For the iomap and xfs parts,
Reviewed-by: Darrick J. Wong <darrick.wong@oracle.com>
That said...
> diff --git a/include/linux/mm_types.h b/include/linux/mm_types.h
> index 54f1e05ecf3e..da2b77a19911 100644
> --- a/include/linux/mm_types.h
> +++ b/include/linux/mm_types.h
> @@ -22,7 +22,8 @@
> #endif
> #define AT_VECTOR_SIZE (2*(AT_VECTOR_SIZE_ARCH + AT_VECTOR_SIZE_BASE + 1))
>
> -typedef int vm_fault_t;
> +typedef unsigned __bitwise vm_fault_t;
> +
>
> struct address_space;
> struct mem_cgroup;
> @@ -619,7 +620,7 @@ struct vm_special_mapping {
> * If non-NULL, then this is called to resolve page faults
> * on the special mapping. If used, .pages is not checked.
> */
> - int (*fault)(const struct vm_special_mapping *sm,
> + vm_fault_t (*fault)(const struct vm_special_mapping *sm,
Uh, we're changing function signatures /and/ redefinining vm_fault_t?
All in the same 90K patch?
I /was/ expecting a series of "convert XXXXX and all callers/users"
patches followed by a trivial one to switch the definition, not a giant
pile of change. FWIW I don't mind so much if you make a patch
containing a change for some super-common primitive and a hojillion
little diff hunks tree-wide, but only one logical change at a time for a
big patch, please...
I quite prefer seeing the whole series from start to finish all packaged
up in one series, but wow this was overwhelming. :/
--D
<ULTRASNIP>
^ permalink raw reply
* [PATCH v6 04/17] media: rkisp1: add Rockchip MIPI Synopsys DPHY driver
From: Tomasz Figa @ 2018-05-16 15:15 UTC (permalink / raw)
To: linux-arm-kernel
In-Reply-To: <CAFLEztTsPdR9mjj7uu+0dUpUecUx=bLRBRcfue06Gy5YdrgGDA@mail.gmail.com>
Hi Jacob,
On Wed, May 16, 2018 at 11:54 PM Jacob Chen <jacobchen110@gmail.com> wrote:
> 2018-05-16 22:39 GMT+08:00 Jacob Chen <jacobchen110@gmail.com>:
> > Hi Laurent,
> >
> > 2018-05-16 13:20 GMT+08:00 Laurent Pinchart <
laurent.pinchart@ideasonboard.com>:
> >> Hi Jacob,
> >>
> >> Thank you for the patch.
> >>
> >> On Thursday, 8 March 2018 11:47:54 EEST Jacob Chen wrote:
> >>> From: Jacob Chen <jacob2.chen@rock-chips.com>
> >>>
> >>> This commit adds a subdev driver for Rockchip MIPI Synopsys DPHY
driver
> >>
> >> Should this really be a subdev driver ? After a quick look at the
code, the
> >> only parameters you need to configure the PHY is the number of lanes
and the
> >> data rate. Implementing the whole subdev API seems overcomplicated to
me,
> >> especially given that the D-PHY doesn't deal with video streams as
such, but
> >> operates one level down. Shouldn't we model the D-PHY using the Linux
PHY
> >> framework ? I believe all the features you need are there except for a
D-PHY-
> >> specific configuration function that should be very easy to add.
> >>
> >
> > It deserves a subdev driver since the ISP is not the only user.
> > Other driver, like VIP, use it too.
> >
> >
> For example, if there are two sensors connected to a rk3399 board.
> Sensor1 --> DPHY1
> Sensor2 --> DPHY2
> With a subdev phy driver, i can choose either ISP or VIP for
> sensor1/sensor2 by enable/disable media link in the run time.
> 1.
> Sensor1 --> DPHY1 ---> VIP
> Sensor2 --> DPHY2 ---> ISP1
> 2.
> Sensor1 --> DPHY1 ---> ISP1
> Sensor2 --> DPHY2 ---> VIP
What is VIP?
Also, if we model the DPHY using the PHY interface, it will be still
possible to achieve the same, just by toggling the link between sensor and
VIP or ISP1:
1.
Sensor1 -------|~|--- VIP
\ | (PHY interface)
\ DPHY1
\ | (PHY interface)
\---| |-- ISP1
Sensor2 -------| |-- VIP
\ | (PHY interface)
\ DPHY2
\ | (PHY interface)
\---|~|-- ISP1
2.
Sensor1 -------| |-- VIP
\ | (PHY interface)
\ DPHY1
\ | (PHY interface)
\---|~|-- ISP1
Sensor2 -------|~|-- VIP
\ | (PHY interface)
\ DPHY2
\ | (PHY interface)
\---| |-- ISP1
Best regards,
Tomasz
^ permalink raw reply
* [PATCH 0/4] arm: mach-omap2: clkdm/pwrdm: Save/restore context
From: Keerthy @ 2018-05-16 15:16 UTC (permalink / raw)
To: linux-arm-kernel
The series adds a save/restore functions for clockdomain
and powerdomain. The context is lost only with AM43XX SoCs
only in the rtc only mode with ddr in self-refresh.
The other OMAP family do not use the contexts as the PRCM
is in WKUP Domain.
Keerthy (2):
omap2: clockdomain: Inroduce cpu_pm notifiers for context save/restore
omap2: powerdomain: Inroduce cpu_pm notifiers for context
save/restore
Russ Dill (2):
ARM: OMAP2: Add functions to save and restore clockdomain context
en-masse.
ARM: OMAP2: Add functions to save and restore powerdomain context
arch/arm/mach-omap2/clockdomain.c | 73 ++++++++++++++++++++++++++++++++
arch/arm/mach-omap2/clockdomain.h | 8 ++++
arch/arm/mach-omap2/cm33xx.c | 53 ++++++++++++++++++++++++
arch/arm/mach-omap2/cminst44xx.c | 43 +++++++++++++++++++
arch/arm/mach-omap2/powerdomain.c | 87 +++++++++++++++++++++++++++++++++++++++
arch/arm/mach-omap2/powerdomain.h | 7 ++++
arch/arm/mach-omap2/prm33xx.c | 31 ++++++++++++++
arch/arm/mach-omap2/prm44xx.c | 50 ++++++++++++++++++++++
8 files changed, 352 insertions(+)
--
1.9.1
^ permalink raw reply
* [PATCH 1/4] ARM: OMAP2: Add functions to save and restore clockdomain context en-masse.
From: Keerthy @ 2018-05-16 15:16 UTC (permalink / raw)
To: linux-arm-kernel
In-Reply-To: <1526483821-25585-1-git-send-email-j-keerthy@ti.com>
From: Russ Dill <Russ.Dill@ti.com>
This is used to support suspend modes like RTC-only and hibernate where
the state of the registers controlling clockdomains is lost.
Signed-off-by: Russ Dill <Russ.Dill@ti.com>
Signed-off-by: Dave Gerlach <d-gerlach@ti.com>
Signed-off-by: Keerthy <j-keerthy@ti.com>
---
arch/arm/mach-omap2/clockdomain.c | 46 +++++++++++++++++++++++++++++++++
arch/arm/mach-omap2/clockdomain.h | 8 ++++++
arch/arm/mach-omap2/cm33xx.c | 53 +++++++++++++++++++++++++++++++++++++++
arch/arm/mach-omap2/cminst44xx.c | 43 +++++++++++++++++++++++++++++++
4 files changed, 150 insertions(+)
diff --git a/arch/arm/mach-omap2/clockdomain.c b/arch/arm/mach-omap2/clockdomain.c
index b79b1ca..0906380 100644
--- a/arch/arm/mach-omap2/clockdomain.c
+++ b/arch/arm/mach-omap2/clockdomain.c
@@ -1307,3 +1307,49 @@ int clkdm_hwmod_disable(struct clockdomain *clkdm, struct omap_hwmod *oh)
return 0;
}
+/**
+ * _clkdm_save_context - save the context for the control of this clkdm
+ *
+ * Due to a suspend or hibernation operation, the state of the registers
+ * controlling this clkdm will be lost, save their context.
+ */
+static int _clkdm_save_context(struct clockdomain *clkdm, void *ununsed)
+{
+ if (!arch_clkdm || !arch_clkdm->clkdm_save_context)
+ return -EINVAL;
+
+ return arch_clkdm->clkdm_save_context(clkdm);
+}
+
+/**
+ * _clkdm_restore_context - restore context for control of this clkdm
+ *
+ * Restore the register values for this clockdomain.
+ */
+static int _clkdm_restore_context(struct clockdomain *clkdm, void *ununsed)
+{
+ if (!arch_clkdm || !arch_clkdm->clkdm_restore_context)
+ return -EINVAL;
+
+ return arch_clkdm->clkdm_restore_context(clkdm);
+}
+
+/**
+ * clkdm_save_context - Saves the context for each registered clkdm
+ *
+ * Save the context for each registered clockdomain.
+ */
+void clkdm_save_context(void)
+{
+ clkdm_for_each(_clkdm_save_context, NULL);
+}
+
+/**
+ * clkdm_restore_context - Restores the context for each registered clkdm
+ *
+ * Restore the context for each registered clockdomain.
+ */
+void clkdm_restore_context(void)
+{
+ clkdm_for_each(_clkdm_restore_context, NULL);
+}
diff --git a/arch/arm/mach-omap2/clockdomain.h b/arch/arm/mach-omap2/clockdomain.h
index 24667a5..c7d0953 100644
--- a/arch/arm/mach-omap2/clockdomain.h
+++ b/arch/arm/mach-omap2/clockdomain.h
@@ -141,6 +141,7 @@ struct clockdomain {
int usecount;
int forcewake_count;
struct list_head node;
+ u32 context;
};
/**
@@ -159,6 +160,8 @@ struct clockdomain {
* @clkdm_deny_idle: Disable hw supervised idle transitions for clock domain
* @clkdm_clk_enable: Put the clkdm in right state for a clock enable
* @clkdm_clk_disable: Put the clkdm in right state for a clock disable
+ * @clkdm_save_context: Save the current clkdm context
+ * @clkdm_restore_context: Restore the clkdm context
*/
struct clkdm_ops {
int (*clkdm_add_wkdep)(struct clockdomain *clkdm1, struct clockdomain *clkdm2);
@@ -175,6 +178,8 @@ struct clkdm_ops {
void (*clkdm_deny_idle)(struct clockdomain *clkdm);
int (*clkdm_clk_enable)(struct clockdomain *clkdm);
int (*clkdm_clk_disable)(struct clockdomain *clkdm);
+ int (*clkdm_save_context)(struct clockdomain *clkdm);
+ int (*clkdm_restore_context)(struct clockdomain *clkdm);
};
int clkdm_register_platform_funcs(struct clkdm_ops *co);
@@ -214,6 +219,9 @@ int clkdm_for_each(int (*fn)(struct clockdomain *clkdm, void *user),
int clkdm_hwmod_enable(struct clockdomain *clkdm, struct omap_hwmod *oh);
int clkdm_hwmod_disable(struct clockdomain *clkdm, struct omap_hwmod *oh);
+void clkdm_save_context(void);
+void clkdm_restore_context(void);
+
extern void __init omap242x_clockdomains_init(void);
extern void __init omap243x_clockdomains_init(void);
extern void __init omap3xxx_clockdomains_init(void);
diff --git a/arch/arm/mach-omap2/cm33xx.c b/arch/arm/mach-omap2/cm33xx.c
index 1cc0247..084d454 100644
--- a/arch/arm/mach-omap2/cm33xx.c
+++ b/arch/arm/mach-omap2/cm33xx.c
@@ -72,6 +72,17 @@ static inline u32 am33xx_cm_rmw_reg_bits(u32 mask, u32 bits, s16 inst, s16 idx)
return v;
}
+static inline u32 am33xx_cm_read_reg_bits(u16 inst, s16 idx, u32 mask)
+{
+ u32 v;
+
+ v = am33xx_cm_read_reg(inst, idx);
+ v &= mask;
+ v >>= __ffs(mask);
+
+ return v;
+}
+
/**
* _clkctrl_idlest - read a CM_*_CLKCTRL register; mask & shift IDLEST bitfield
* @inst: CM instance register offset (*_INST macro)
@@ -338,6 +349,46 @@ static u32 am33xx_cm_xlate_clkctrl(u8 part, u16 inst, u16 offset)
return cm_base.pa + inst + offset;
}
+/**
+ * am33xx_clkdm_save_context - Save the clockdomain transition context
+ * @clkdm: The clockdomain pointer whose context needs to be saved
+ *
+ * Save the clockdomain transition context.
+ */
+static int am33xx_clkdm_save_context(struct clockdomain *clkdm)
+{
+ clkdm->context = am33xx_cm_read_reg_bits(clkdm->cm_inst,
+ clkdm->clkdm_offs,
+ AM33XX_CLKTRCTRL_MASK);
+
+ return 0;
+}
+
+/**
+ * am33xx_restore_save_context - Restore the clockdomain transition context
+ * @clkdm: The clockdomain pointer whose context needs to be restored
+ *
+ * Restore the clockdomain transition context.
+ */
+static int am33xx_clkdm_restore_context(struct clockdomain *clkdm)
+{
+ switch (clkdm->context) {
+ case OMAP34XX_CLKSTCTRL_DISABLE_AUTO:
+ am33xx_clkdm_deny_idle(clkdm);
+ break;
+ case OMAP34XX_CLKSTCTRL_FORCE_SLEEP:
+ am33xx_clkdm_sleep(clkdm);
+ break;
+ case OMAP34XX_CLKSTCTRL_FORCE_WAKEUP:
+ am33xx_clkdm_wakeup(clkdm);
+ break;
+ case OMAP34XX_CLKSTCTRL_ENABLE_AUTO:
+ am33xx_clkdm_allow_idle(clkdm);
+ break;
+ }
+ return 0;
+}
+
struct clkdm_ops am33xx_clkdm_operations = {
.clkdm_sleep = am33xx_clkdm_sleep,
.clkdm_wakeup = am33xx_clkdm_wakeup,
@@ -345,6 +396,8 @@ struct clkdm_ops am33xx_clkdm_operations = {
.clkdm_deny_idle = am33xx_clkdm_deny_idle,
.clkdm_clk_enable = am33xx_clkdm_clk_enable,
.clkdm_clk_disable = am33xx_clkdm_clk_disable,
+ .clkdm_save_context = am33xx_clkdm_save_context,
+ .clkdm_restore_context = am33xx_clkdm_restore_context,
};
static const struct cm_ll_data am33xx_cm_ll_data = {
diff --git a/arch/arm/mach-omap2/cminst44xx.c b/arch/arm/mach-omap2/cminst44xx.c
index 7deefee..c11ac49 100644
--- a/arch/arm/mach-omap2/cminst44xx.c
+++ b/arch/arm/mach-omap2/cminst44xx.c
@@ -481,6 +481,47 @@ static u32 omap4_cminst_xlate_clkctrl(u8 part, u16 inst, u16 offset)
return _cm_bases[part].pa + inst + offset;
}
+/**
+ * omap4_clkdm_save_context - Save the clockdomain modulemode context
+ * @clkdm: The clockdomain pointer whose context needs to be saved
+ *
+ * Save the clockdomain modulemode context.
+ */
+static int omap4_clkdm_save_context(struct clockdomain *clkdm)
+{
+ clkdm->context = omap4_cminst_read_inst_reg(clkdm->prcm_partition,
+ clkdm->cm_inst,
+ clkdm->clkdm_offs +
+ OMAP4_CM_CLKSTCTRL);
+ clkdm->context &= OMAP4430_MODULEMODE_MASK;
+ return 0;
+}
+
+/**
+ * omap4_clkdm_restore_context - Restore the clockdomain modulemode context
+ * @clkdm: The clockdomain pointer whose context needs to be restored
+ *
+ * Restore the clockdomain modulemode context.
+ */
+static int omap4_clkdm_restore_context(struct clockdomain *clkdm)
+{
+ switch (clkdm->context) {
+ case OMAP34XX_CLKSTCTRL_DISABLE_AUTO:
+ omap4_clkdm_deny_idle(clkdm);
+ break;
+ case OMAP34XX_CLKSTCTRL_FORCE_SLEEP:
+ omap4_clkdm_sleep(clkdm);
+ break;
+ case OMAP34XX_CLKSTCTRL_FORCE_WAKEUP:
+ omap4_clkdm_wakeup(clkdm);
+ break;
+ case OMAP34XX_CLKSTCTRL_ENABLE_AUTO:
+ omap4_clkdm_allow_idle(clkdm);
+ break;
+ }
+ return 0;
+}
+
struct clkdm_ops omap4_clkdm_operations = {
.clkdm_add_wkdep = omap4_clkdm_add_wkup_sleep_dep,
.clkdm_del_wkdep = omap4_clkdm_del_wkup_sleep_dep,
@@ -496,6 +537,8 @@ struct clkdm_ops omap4_clkdm_operations = {
.clkdm_deny_idle = omap4_clkdm_deny_idle,
.clkdm_clk_enable = omap4_clkdm_clk_enable,
.clkdm_clk_disable = omap4_clkdm_clk_disable,
+ .clkdm_save_context = omap4_clkdm_save_context,
+ .clkdm_restore_context = omap4_clkdm_restore_context,
};
struct clkdm_ops am43xx_clkdm_operations = {
--
1.9.1
^ permalink raw reply related
* [PATCH 2/4] omap2: clockdomain: Inroduce cpu_pm notifiers for context save/restore
From: Keerthy @ 2018-05-16 15:16 UTC (permalink / raw)
To: linux-arm-kernel
In-Reply-To: <1526483821-25585-1-git-send-email-j-keerthy@ti.com>
Inroduce cpu_pm notifiers for context save/restore. This will be
needed for am43xx family in case of rtc only mode with ddr in
self-refresh.
Signed-off-by: Keerthy <j-keerthy@ti.com>
---
arch/arm/mach-omap2/clockdomain.c | 27 +++++++++++++++++++++++++++
1 file changed, 27 insertions(+)
diff --git a/arch/arm/mach-omap2/clockdomain.c b/arch/arm/mach-omap2/clockdomain.c
index 0906380..6d44fe0 100644
--- a/arch/arm/mach-omap2/clockdomain.c
+++ b/arch/arm/mach-omap2/clockdomain.c
@@ -23,6 +23,7 @@
#include <linux/limits.h>
#include <linux/err.h>
#include <linux/clk-provider.h>
+#include <linux/cpu_pm.h>
#include <linux/io.h>
@@ -31,6 +32,7 @@
#include "soc.h"
#include "clock.h"
#include "clockdomain.h"
+#include "pm.h"
/* clkdm_list contains all registered struct clockdomains */
static LIST_HEAD(clkdm_list);
@@ -39,6 +41,8 @@
static struct clkdm_autodep *autodeps;
static struct clkdm_ops *arch_clkdm;
+void clkdm_save_context(void);
+void clkdm_restore_context(void);
/* Private functions */
@@ -449,6 +453,22 @@ int clkdm_register_autodeps(struct clkdm_autodep *ia)
return 0;
}
+static int cpu_notifier(struct notifier_block *nb, unsigned long cmd, void *v)
+{
+ switch (cmd) {
+ case CPU_CLUSTER_PM_ENTER:
+ if (enable_off_mode)
+ clkdm_save_context();
+ break;
+ case CPU_CLUSTER_PM_EXIT:
+ if (enable_off_mode)
+ clkdm_restore_context();
+ break;
+ }
+
+ return NOTIFY_OK;
+}
+
/**
* clkdm_complete_init - set up the clockdomain layer
*
@@ -460,6 +480,7 @@ int clkdm_register_autodeps(struct clkdm_autodep *ia)
int clkdm_complete_init(void)
{
struct clockdomain *clkdm;
+ static struct notifier_block nb;
if (list_empty(&clkdm_list))
return -EACCES;
@@ -474,6 +495,12 @@ int clkdm_complete_init(void)
clkdm_clear_all_sleepdeps(clkdm);
}
+ /* Only AM43XX can lose clkdm context during rtc-ddr suspend */
+ if (soc_is_am43xx()) {
+ nb.notifier_call = cpu_notifier;
+ cpu_pm_register_notifier(&nb);
+ }
+
return 0;
}
--
1.9.1
^ permalink raw reply related
* [PATCH 3/4] ARM: OMAP2: Add functions to save and restore powerdomain context
From: Keerthy @ 2018-05-16 15:17 UTC (permalink / raw)
To: linux-arm-kernel
In-Reply-To: <1526483821-25585-1-git-send-email-j-keerthy@ti.com>
From: Russ Dill <Russ.Dill@ti.com>
The powerdomain control registers are stored in the WKUP powerdomain on
AM33XX/AM43XX, which is lost on RTC-only suspend and also hibernate. This
adds context save and restore functions for those registers.
Sometimes the powerdomain state does not need to change,
perhaps we only need to change memory retention states, so make
sure the restored state is different from the current state before we wait
for a transition.
Signed-off-by: Keerthy <j-keerthy@ti.com>
Signed-off-by: Dave Gerlach <d-gerlach@ti.com>
Signed-off-by: Russ Dill <Russ.Dill@ti.com>
---
arch/arm/mach-omap2/powerdomain.c | 60 +++++++++++++++++++++++++++++++++++++++
arch/arm/mach-omap2/powerdomain.h | 7 +++++
arch/arm/mach-omap2/prm33xx.c | 31 ++++++++++++++++++++
arch/arm/mach-omap2/prm44xx.c | 50 ++++++++++++++++++++++++++++++++
4 files changed, 148 insertions(+)
diff --git a/arch/arm/mach-omap2/powerdomain.c b/arch/arm/mach-omap2/powerdomain.c
index 1e6a967..b97b308 100644
--- a/arch/arm/mach-omap2/powerdomain.c
+++ b/arch/arm/mach-omap2/powerdomain.c
@@ -1199,3 +1199,63 @@ bool pwrdm_can_ever_lose_context(struct powerdomain *pwrdm)
return 0;
}
+
+/**
+ * pwrdm_save_context - save powerdomain registers
+ *
+ * Register state is going to be lost due to a suspend or hibernate
+ * event. Save the powerdomain registers.
+ */
+static int pwrdm_save_context(struct powerdomain *pwrdm, void *unused)
+{
+ if (arch_pwrdm && arch_pwrdm->pwrdm_save_context)
+ arch_pwrdm->pwrdm_save_context(pwrdm);
+ return 0;
+}
+
+/**
+ * pwrdm_save_context - restore powerdomain registers
+ *
+ * Restore powerdomain control registers after a suspend or resume
+ * event.
+ */
+static int pwrdm_restore_context(struct powerdomain *pwrdm, void *unused)
+{
+ if (arch_pwrdm && arch_pwrdm->pwrdm_restore_context)
+ arch_pwrdm->pwrdm_restore_context(pwrdm);
+ return 0;
+}
+
+static int pwrdm_lost_power(struct powerdomain *pwrdm, void *unused)
+{
+ int state;
+
+ /*
+ * Power has been lost across all powerdomains, increment the
+ * counter.
+ */
+
+ state = pwrdm_read_pwrst(pwrdm);
+ if (state != PWRDM_POWER_OFF) {
+ pwrdm->state_counter[state]++;
+ pwrdm->state_counter[PWRDM_POWER_OFF]++;
+ }
+ pwrdm->state = state;
+
+ return 0;
+}
+
+void pwrdms_save_context(void)
+{
+ pwrdm_for_each(pwrdm_save_context, NULL);
+}
+
+void pwrdms_restore_context(void)
+{
+ pwrdm_for_each(pwrdm_restore_context, NULL);
+}
+
+void pwrdms_lost_power(void)
+{
+ pwrdm_for_each(pwrdm_lost_power, NULL);
+}
diff --git a/arch/arm/mach-omap2/powerdomain.h b/arch/arm/mach-omap2/powerdomain.h
index 28a796c..9a907fb 100644
--- a/arch/arm/mach-omap2/powerdomain.h
+++ b/arch/arm/mach-omap2/powerdomain.h
@@ -144,6 +144,7 @@ struct powerdomain {
s64 timer;
s64 state_timer[PWRDM_MAX_PWRSTS];
#endif
+ u32 context;
};
/**
@@ -198,6 +199,8 @@ struct pwrdm_ops {
int (*pwrdm_set_lowpwrstchange)(struct powerdomain *pwrdm);
int (*pwrdm_wait_transition)(struct powerdomain *pwrdm);
int (*pwrdm_has_voltdm)(void);
+ void (*pwrdm_save_context)(struct powerdomain *pwrdm);
+ void (*pwrdm_restore_context)(struct powerdomain *pwrdm);
};
int pwrdm_register_platform_funcs(struct pwrdm_ops *custom_funcs);
@@ -273,4 +276,8 @@ u8 pwrdm_get_valid_lp_state(struct powerdomain *pwrdm,
extern void pwrdm_lock(struct powerdomain *pwrdm);
extern void pwrdm_unlock(struct powerdomain *pwrdm);
+extern void pwrdms_save_context(void);
+extern void pwrdms_restore_context(void);
+
+extern void pwrdms_lost_power(void);
#endif
diff --git a/arch/arm/mach-omap2/prm33xx.c b/arch/arm/mach-omap2/prm33xx.c
index ebaf80d..d514166 100644
--- a/arch/arm/mach-omap2/prm33xx.c
+++ b/arch/arm/mach-omap2/prm33xx.c
@@ -342,6 +342,35 @@ static void am33xx_prm_global_warm_sw_reset(void)
AM33XX_PRM_RSTCTRL_OFFSET);
}
+static void am33xx_pwrdm_save_context(struct powerdomain *pwrdm)
+{
+ pwrdm->context = am33xx_prm_read_reg(pwrdm->prcm_offs,
+ pwrdm->pwrstctrl_offs);
+ /*
+ * Do not save LOWPOWERSTATECHANGE, writing a 1 indicates a request,
+ * reading back a 1 indicates a request in progress.
+ */
+ pwrdm->context &= ~AM33XX_LOWPOWERSTATECHANGE_MASK;
+}
+
+static void am33xx_pwrdm_restore_context(struct powerdomain *pwrdm)
+{
+ int st, ctrl;
+
+ st = am33xx_prm_read_reg(pwrdm->prcm_offs,
+ pwrdm->pwrstst_offs);
+
+ am33xx_prm_write_reg(pwrdm->context, pwrdm->prcm_offs,
+ pwrdm->pwrstctrl_offs);
+
+ /* Make sure we only wait for a transition if there is one */
+ st &= OMAP_POWERSTATEST_MASK;
+ ctrl = OMAP_POWERSTATEST_MASK & pwrdm->context;
+
+ if (st != ctrl)
+ am33xx_pwrdm_wait_transition(pwrdm);
+}
+
struct pwrdm_ops am33xx_pwrdm_operations = {
.pwrdm_set_next_pwrst = am33xx_pwrdm_set_next_pwrst,
.pwrdm_read_next_pwrst = am33xx_pwrdm_read_next_pwrst,
@@ -357,6 +386,8 @@ struct pwrdm_ops am33xx_pwrdm_operations = {
.pwrdm_set_mem_retst = am33xx_pwrdm_set_mem_retst,
.pwrdm_wait_transition = am33xx_pwrdm_wait_transition,
.pwrdm_has_voltdm = am33xx_check_vcvp,
+ .pwrdm_save_context = am33xx_pwrdm_save_context,
+ .pwrdm_restore_context = am33xx_pwrdm_restore_context,
};
static struct prm_ll_data am33xx_prm_ll_data = {
diff --git a/arch/arm/mach-omap2/prm44xx.c b/arch/arm/mach-omap2/prm44xx.c
index acb9593..47b657c 100644
--- a/arch/arm/mach-omap2/prm44xx.c
+++ b/arch/arm/mach-omap2/prm44xx.c
@@ -667,6 +667,54 @@ static int omap4_check_vcvp(void)
return 0;
}
+/**
+ * omap4_pwrdm_save_context - Saves the powerdomain state
+ * @pwrdm: pointer to individual powerdomain
+ *
+ * The function saves the powerdomain state control information.
+ * This is needed in rtc+ddr modes where we lose powerdomain context.
+ */
+static void omap4_pwrdm_save_context(struct powerdomain *pwrdm)
+{
+ pwrdm->context = omap4_prminst_read_inst_reg(pwrdm->prcm_partition,
+ pwrdm->prcm_offs,
+ pwrdm->pwrstctrl_offs);
+
+ /*
+ * Do not save LOWPOWERSTATECHANGE, writing a 1 indicates a request,
+ * reading back a 1 indicates a request in progress.
+ */
+ pwrdm->context &= ~OMAP4430_LOWPOWERSTATECHANGE_MASK;
+}
+
+/**
+ * omap4_pwrdm_restore_context - Restores the powerdomain state
+ * @pwrdm: pointer to individual powerdomain
+ *
+ * The function restores the powerdomain state control information.
+ * This is needed in rtc+ddr modes where we lose powerdomain context.
+ */
+static void omap4_pwrdm_restore_context(struct powerdomain *pwrdm)
+{
+ int st, ctrl;
+
+ st = omap4_prminst_read_inst_reg(pwrdm->prcm_partition,
+ pwrdm->prcm_offs,
+ pwrdm->pwrstctrl_offs);
+
+ omap4_prminst_write_inst_reg(pwrdm->context,
+ pwrdm->prcm_partition,
+ pwrdm->prcm_offs,
+ pwrdm->pwrstctrl_offs);
+
+ /* Make sure we only wait for a transition if there is one */
+ st &= OMAP_POWERSTATEST_MASK;
+ ctrl = OMAP_POWERSTATEST_MASK & pwrdm->context;
+
+ if (st != ctrl)
+ omap4_pwrdm_wait_transition(pwrdm);
+}
+
struct pwrdm_ops omap4_pwrdm_operations = {
.pwrdm_set_next_pwrst = omap4_pwrdm_set_next_pwrst,
.pwrdm_read_next_pwrst = omap4_pwrdm_read_next_pwrst,
@@ -685,6 +733,8 @@ struct pwrdm_ops omap4_pwrdm_operations = {
.pwrdm_set_mem_retst = omap4_pwrdm_set_mem_retst,
.pwrdm_wait_transition = omap4_pwrdm_wait_transition,
.pwrdm_has_voltdm = omap4_check_vcvp,
+ .pwrdm_save_context = omap4_pwrdm_save_context,
+ .pwrdm_restore_context = omap4_pwrdm_restore_context,
};
static int omap44xx_prm_late_init(void);
--
1.9.1
^ permalink raw reply related
* [PATCH 4/4] omap2: powerdomain: Inroduce cpu_pm notifiers for context save/restore
From: Keerthy @ 2018-05-16 15:17 UTC (permalink / raw)
To: linux-arm-kernel
In-Reply-To: <1526483821-25585-1-git-send-email-j-keerthy@ti.com>
Inroduce cpu_pm notifiers for context save/restore. This is
needed for am43xx family during rtc only mode with ddr in
self-refresh.
Signed-off-by: Keerthy <j-keerthy@ti.com>
---
arch/arm/mach-omap2/powerdomain.c | 27 +++++++++++++++++++++++++++
1 file changed, 27 insertions(+)
diff --git a/arch/arm/mach-omap2/powerdomain.c b/arch/arm/mach-omap2/powerdomain.c
index b97b308..1a0f69c 100644
--- a/arch/arm/mach-omap2/powerdomain.c
+++ b/arch/arm/mach-omap2/powerdomain.c
@@ -14,6 +14,7 @@
*/
#undef DEBUG
+#include <linux/cpu_pm.h>
#include <linux/kernel.h>
#include <linux/types.h>
#include <linux/list.h>
@@ -39,6 +40,9 @@
#define PWRDM_TRACE_STATES_FLAG (1<<31)
+void pwrdms_save_context(void);
+void pwrdms_restore_context(void);
+
enum {
PWRDM_STATE_NOW = 0,
PWRDM_STATE_PREV,
@@ -333,6 +337,22 @@ int pwrdm_register_pwrdms(struct powerdomain **ps)
return 0;
}
+static int cpu_notifier(struct notifier_block *nb, unsigned long cmd, void *v)
+{
+ switch (cmd) {
+ case CPU_CLUSTER_PM_ENTER:
+ if (enable_off_mode)
+ pwrdms_save_context();
+ break;
+ case CPU_CLUSTER_PM_EXIT:
+ if (enable_off_mode)
+ pwrdms_restore_context();
+ break;
+ }
+
+ return NOTIFY_OK;
+}
+
/**
* pwrdm_complete_init - set up the powerdomain layer
*
@@ -347,6 +367,7 @@ int pwrdm_register_pwrdms(struct powerdomain **ps)
int pwrdm_complete_init(void)
{
struct powerdomain *temp_p;
+ static struct notifier_block nb;
if (list_empty(&pwrdm_list))
return -EACCES;
@@ -354,6 +375,12 @@ int pwrdm_complete_init(void)
list_for_each_entry(temp_p, &pwrdm_list, node)
pwrdm_set_next_pwrst(temp_p, PWRDM_POWER_ON);
+ /* Only AM43XX can lose pwrdm context during rtc-ddr suspend */
+ if (soc_is_am43xx()) {
+ nb.notifier_call = cpu_notifier;
+ cpu_pm_register_notifier(&nb);
+ }
+
return 0;
}
--
1.9.1
^ permalink raw reply related
* [PATCH 2/2] pinctrl: pinctrl-single: Add functions to save and restore pinctrl context
From: Tony Lindgren @ 2018-05-16 15:22 UTC (permalink / raw)
To: linux-arm-kernel
In-Reply-To: <1526449230-27618-3-git-send-email-j-keerthy@ti.com>
* Keerthy <j-keerthy@ti.com> [180516 05:43]:
> This adds a pair of context save/restore functions to save/restore the
> state of a set of pinctrl registers. This simplifies some of the AM33XX
> PM code as some of the pinctrl registers are lost when the per power
> domain loses power. The pincrtl code can perform the necessary
> save/restore.
>
> This will also be necessary for hibernation and RTC only sleep, as all
> pinctrl registers all lost.
I think for now you can get away with just adding suspend and resume
calls where suspend calls pdata->context_may_be_lost() and saves
context as needed. And you do want to still call context_may_be_lost()
as Android configured kernel does suspend and resume constantly..
Then the cpu_notifier() can be added later on as needed once the
cpuidle patches are ready.
Regards,
Tony
^ permalink raw reply
* [PATCH v4] pinctrl: msm: fix gpio-hog related boot issues
From: Stephen Boyd @ 2018-05-16 15:31 UTC (permalink / raw)
To: linux-arm-kernel
In-Reply-To: <CACRpkdZPEHvnQdwh5Uu5kwqK7yVGTaw_cZR+GOzsG9pg4oEktA@mail.gmail.com>
Quoting Linus Walleij (2018-04-26 05:03:45)
> On Thu, Apr 12, 2018 at 9:01 PM, Christian Lamparter <chunkeey@gmail.com> wrote:
>
> > Sven Eckelmann reported an issue with the current IPQ4019 pinctrl.
> > Setting up any gpio-hog in the device-tree for his device would
> > "kill the bootup completely":
> >
> > | [ 0.477838] msm_serial 78af000.serial: could not find pctldev for node /soc/pinctrl at 1000000/serial_pinmux, deferring probe
> > | [ 0.499828] spi_qup 78b5000.spi: could not find pctldev for node /soc/pinctrl at 1000000/spi_0_pinmux, deferring probe
> > | [ 1.298883] requesting hog GPIO enable USB2 power (chip 1000000.pinctrl, offset 58) failed, -517
> > | [ 1.299609] gpiochip_add_data: GPIOs 0..99 (1000000.pinctrl) failed to register
> > | [ 1.308589] ipq4019-pinctrl 1000000.pinctrl: Failed register gpiochip
> > | [ 1.316586] msm_serial 78af000.serial: could not find pctldev for node /soc/pinctrl at 1000000/serial_pinmux, deferring probe
> > | [ 1.322415] spi_qup 78b5000.spi: could not find pctldev for node /soc/pinctrl at 1000000/spi_0_pinmux, deferri
> >
> > This was also verified on a RT-AC58U (IPQ4018) which would
> > no longer boot, if a gpio-hog was specified. (Tried forcing
> > the USB LED PIN (GPIO0) to high.).
> >
> > The problem is that Pinctrl+GPIO registration is currently
> > peformed in the following order in pinctrl-msm.c:
> > 1. pinctrl_register()
> > 2. gpiochip_add()
> > 3. gpiochip_add_pin_range()
> >
> > The actual error code -517 == -EPROBE_DEFER is coming from
> > pinctrl_get_device_gpio_range(), which is called through:
> > gpiochip_add
> > of_gpiochip_add
> > of_gpiochip_scan_gpios
> > gpiod_hog
> > gpiochip_request_own_desc
> > __gpiod_request
> > chip->request
> > gpiochip_generic_request
> > pinctrl_gpio_request
> > pinctrl_get_device_gpio_range
> >
> > pinctrl_get_device_gpio_range() is unable to find any valid
> > pin ranges, since nothing has been added to the pinctrldev_list yet.
> > so the range can't be found, and the operation fails with -EPROBE_DEFER.
> >
> > This patch fixes the issue by adding the "gpio-ranges" property to
> > the pinctrl device node of all upstream Qcom SoC. The pin ranges are
> > then added by the gpio core.
> >
> > In order to remain compatible with older, existing DTs (and ACPI)
> > a check for the "gpio-ranges" property has been added to
> > msm_gpio_init(). This prevents the driver of adding the same entry
> > to the pinctrldev_list twice.
> >
> > Reported-by: Sven Eckelmann <sven.eckelmann@openmesh.com>
> > Signed-off-by: Christian Lamparter <chunkeey@gmail.com>
>
> This patch looks VERY good to me.
>
Why can't we register the gpiochip and tell it about the pin ranges in
one API call instead of adding the chip and then adding the ranges? It
doesn't look right to have to go and update all the DT nodes to list
this information that is already known in the driver because the kernel
implementation can't handle the order of operations correctly.
Furthermore, it looks like this becomes a silent requirement to add the
gpio-ranges property into the DT so that hogs work, but none of the
bindings have been updated in this patch to indicate that.
^ permalink raw reply
* [PATCH v3 07/12] ACPI / APEI: Make the nmi_fixmap_idx per-ghes to allow multiple in_nmi() users
From: Tyler Baicar @ 2018-05-16 15:38 UTC (permalink / raw)
To: linux-arm-kernel
In-Reply-To: <20180516110348.GA17092@pd.tnic>
On 5/16/2018 7:05 AM, Borislav Petkov wrote:
> On Tue, May 08, 2018 at 09:45:01AM +0100, James Morse wrote:
>> Alternatively, I can put the fixmap-page and spinlock in some 'struct
>> ghes_notification' that only the NMI-like struct-ghes need. This is just moving
>> the indirection up a level, but it does pair the lock with the thing it locks,
>> and gets rid of assigning spinlock pointers.
> Keeping the lock and what it protects in one place certainly sounds
> better. I guess you could so something like this:
>
> struct ghes_fixmap {
> union {
> raw_spinlock_t nmi_lock;
> spinlock_t lock;
> };
> void __iomem *(map)(struct ghes_fixmap *);
> };
>
> and assign the proper ghes_ioremap function to ->map.
>
> The spin_lock_irqsave() call in ghes_copy_tofrom_phys() is kinda
> questionable. Because we should have disabled interrupts so that you can
> do
>
> spin_lock(map->lock);
>
> Except that we do get called with IRQs on and looking at that call of
> ghes_proc() at the end of ghes_probe(), that's a deadlock waiting to
> happen.
>
> And that comes from:
>
> 77b246b32b2c ("acpi: apei: check for pending errors when probing GHES entries")
>
> Tyler, this can't work in any context: imagine the GHES NMI or IRQ or
> the timer fires while that ghes_proc() runs...
>
> What's up?
Hello Boris,
I haven't seen a deadlock from that, but it looks possible. What if the
ghes_proc() call in ghes_probe()
is moved before the second switch statement? That way it is before the
NMI/IRQ/poll is setup. At quick
glance I think that should avoid the deadlock and still provide the
functionality that call was added for. I
can test that out if you all agree.
Thanks,
Tyler
--
Qualcomm Datacenter Technologies, Inc. as an affiliate of Qualcomm Technologies, Inc.
Qualcomm Technologies, Inc. is a member of the Code Aurora Forum,
a Linux Foundation Collaborative Project.
^ permalink raw reply
* [PATCH 0/2] ARM64: dts: meson-axg: i2c clean-up
From: Jerome Brunet @ 2018-05-16 15:52 UTC (permalink / raw)
To: linux-arm-kernel
This patchset fixes a few problems found in the i2c nodes of
amlogic's meson-axg paltform.
Jerome Brunet (2):
ARM64: dts: meson-axg: clean-up i2c nodes
ARM64: dts: meson-axg: remove incorrect i2c ao clock
arch/arm64/boot/dts/amlogic/meson-axg.dtsi | 42 ++++++++++++++----------------
1 file changed, 19 insertions(+), 23 deletions(-)
--
2.14.3
^ permalink raw reply
* [PATCH 1/2] ARM64: dts: meson-axg: clean-up i2c nodes
From: Jerome Brunet @ 2018-05-16 15:52 UTC (permalink / raw)
To: linux-arm-kernel
In-Reply-To: <20180516155203.24922-1-jbrunet@baylibre.com>
Remove undocumented and unused "clk_i2c" clock name and second interrupt
from i2c nodes of meson-axg platform
Those seems to have been copy/pasted from the vendor kernel
Fixes: dc6f858e2690 ("ARM64: dts: meson-axg: add I2C DT info for Meson-AXG SoC")
Signed-off-by: Jerome Brunet <jbrunet@baylibre.com>
---
arch/arm64/boot/dts/amlogic/meson-axg.dtsi | 37 +++++++++++-------------------
1 file changed, 14 insertions(+), 23 deletions(-)
diff --git a/arch/arm64/boot/dts/amlogic/meson-axg.dtsi b/arch/arm64/boot/dts/amlogic/meson-axg.dtsi
index 381bd2c707a7..b59f341104d7 100644
--- a/arch/arm64/boot/dts/amlogic/meson-axg.dtsi
+++ b/arch/arm64/boot/dts/amlogic/meson-axg.dtsi
@@ -214,50 +214,42 @@
i2c0: i2c at 1f000 {
compatible = "amlogic,meson-axg-i2c";
- status = "disabled";
reg = <0x0 0x1f000 0x0 0x20>;
- interrupts = <GIC_SPI 21 IRQ_TYPE_EDGE_RISING>,
- <GIC_SPI 47 IRQ_TYPE_EDGE_RISING>;
+ interrupts = <GIC_SPI 21 IRQ_TYPE_EDGE_RISING>;
+ clocks = <&clkc CLKID_I2C>;
#address-cells = <1>;
#size-cells = <0>;
- clocks = <&clkc CLKID_I2C>;
- clock-names = "clk_i2c";
+ status = "disabled";
};
i2c1: i2c at 1e000 {
compatible = "amlogic,meson-axg-i2c";
+ reg = <0x0 0x1e000 0x0 0x20>;
+ interrupts = <GIC_SPI 214 IRQ_TYPE_EDGE_RISING>;
+ clocks = <&clkc CLKID_I2C>;
#address-cells = <1>;
#size-cells = <0>;
- reg = <0x0 0x1e000 0x0 0x20>;
status = "disabled";
- interrupts = <GIC_SPI 214 IRQ_TYPE_EDGE_RISING>,
- <GIC_SPI 48 IRQ_TYPE_EDGE_RISING>;
- clocks = <&clkc CLKID_I2C>;
- clock-names = "clk_i2c";
};
i2c2: i2c at 1d000 {
compatible = "amlogic,meson-axg-i2c";
- status = "disabled";
reg = <0x0 0x1d000 0x0 0x20>;
- interrupts = <GIC_SPI 215 IRQ_TYPE_EDGE_RISING>,
- <GIC_SPI 49 IRQ_TYPE_EDGE_RISING>;
+ interrupts = <GIC_SPI 215 IRQ_TYPE_EDGE_RISING>;
+ clocks = <&clkc CLKID_I2C>;
#address-cells = <1>;
#size-cells = <0>;
- clocks = <&clkc CLKID_I2C>;
- clock-names = "clk_i2c";
+ status = "disabled";
};
i2c3: i2c at 1c000 {
compatible = "amlogic,meson-axg-i2c";
- status = "disabled";
reg = <0x0 0x1c000 0x0 0x20>;
- interrupts = <GIC_SPI 39 IRQ_TYPE_EDGE_RISING>,
- <GIC_SPI 50 IRQ_TYPE_EDGE_RISING>;
+ interrupts = <GIC_SPI 39 IRQ_TYPE_EDGE_RISING>;
+ clocks = <&clkc CLKID_I2C>;
#address-cells = <1>;
#size-cells = <0>;
- clocks = <&clkc CLKID_I2C>;
- clock-names = "clk_i2c";
+ status = "disabled";
};
uart_A: serial at 24000 {
@@ -1116,13 +1108,12 @@
i2c_AO: i2c at 5000 {
compatible = "amlogic,meson-axg-i2c";
- status = "disabled";
reg = <0x0 0x05000 0x0 0x20>;
interrupts = <GIC_SPI 195 IRQ_TYPE_EDGE_RISING>;
+ clocks = <&clkc CLKID_I2C>;
#address-cells = <1>;
#size-cells = <0>;
- clocks = <&clkc CLKID_I2C>;
- clock-names = "clk_i2c";
+ status = "disabled";
};
uart_AO: serial at 3000 {
--
2.14.3
^ permalink raw reply related
* [PATCH 2/2] ARM64: dts: meson-axg: remove incorrect i2c ao clock
From: Jerome Brunet @ 2018-05-16 15:52 UTC (permalink / raw)
To: linux-arm-kernel
In-Reply-To: <20180516155203.24922-1-jbrunet@baylibre.com>
The clock specified for the i2c AO controller is the one from
the EE domain, which is incorrect as this controller needs the
clock from AO clock controller.
Replace with xtal to avoid claiming an unrelated gate until the
required clock controller becomes available.
Fixes: dc6f858e2690 ("ARM64: dts: meson-axg: add I2C DT info for Meson-AXG SoC")
Signed-off-by: Jerome Brunet <jbrunet@baylibre.com>
---
arch/arm64/boot/dts/amlogic/meson-axg.dtsi | 7 ++++++-
1 file changed, 6 insertions(+), 1 deletion(-)
diff --git a/arch/arm64/boot/dts/amlogic/meson-axg.dtsi b/arch/arm64/boot/dts/amlogic/meson-axg.dtsi
index b59f341104d7..b82b9d79ec49 100644
--- a/arch/arm64/boot/dts/amlogic/meson-axg.dtsi
+++ b/arch/arm64/boot/dts/amlogic/meson-axg.dtsi
@@ -1110,7 +1110,12 @@
compatible = "amlogic,meson-axg-i2c";
reg = <0x0 0x05000 0x0 0x20>;
interrupts = <GIC_SPI 195 IRQ_TYPE_EDGE_RISING>;
- clocks = <&clkc CLKID_I2C>;
+
+ /*
+ * FIXME: replace with the correct clock when
+ * the controller is available
+ */
+ clocks = <&xtal>;
#address-cells = <1>;
#size-cells = <0>;
status = "disabled";
--
2.14.3
^ permalink raw reply related
* [PATCH 01/14] ARM: add CPU part numbers for Cortex A73, A75 and Brahma B15
From: Florian Fainelli @ 2018-05-16 16:19 UTC (permalink / raw)
To: linux-arm-kernel
In-Reply-To: <E1fIuAk-0003ja-BC@rmk-PC.armlinux.org.uk>
On 05/16/2018 04:00 AM, Russell King wrote:
> Add CPU part numbers for the above mentioned CPUs
>
> Signed-off-by: Russell King <rmk+kernel@armlinux.org.uk>
> ---
> arch/arm/include/asm/cputype.h | 5 +++++
> 1 file changed, 5 insertions(+)
>
> diff --git a/arch/arm/include/asm/cputype.h b/arch/arm/include/asm/cputype.h
> index cb546425da8a..adc4a3eef815 100644
> --- a/arch/arm/include/asm/cputype.h
> +++ b/arch/arm/include/asm/cputype.h
> @@ -77,8 +77,13 @@
> #define ARM_CPU_PART_CORTEX_A12 0x4100c0d0
> #define ARM_CPU_PART_CORTEX_A17 0x4100c0e0
> #define ARM_CPU_PART_CORTEX_A15 0x4100c0f0
> +#define ARM_CPU_PART_CORTEX_A73 0x4100d090
> +#define ARM_CPU_PART_CORTEX_A75 0x4100d0a0
> #define ARM_CPU_PART_MASK 0xff00fff0
>
> +/* Broadcom cores */
> +#define ARM_CPU_PART_BRAHMA_B15 0x420000f0
For B15:
Acked-by: Florian Fainelli <f.fainelli@gmail.com>
> +
> /* DEC implemented cores */
> #define ARM_CPU_PART_SA1100 0x4400a110
>
>
--
Florian
^ permalink raw reply
* [PATCH 02/14] ARM: bugs: prepare processor bug infrastructure
From: Florian Fainelli @ 2018-05-16 16:21 UTC (permalink / raw)
To: linux-arm-kernel
In-Reply-To: <E1fIuAp-0003jj-H5@rmk-PC.armlinux.org.uk>
On 05/16/2018 04:00 AM, Russell King wrote:
> Prepare the processor bug infrastructure so that it can be expanded to
> check for per-processor bugs.
>
> Signed-off-by: Russell King <rmk+kernel@armlinux.org.uk>
Reviewed-by: Florian Fainelli <f.fainelli@gmail.com>
--
Florian
^ permalink raw reply
* [PATCH 03/14] ARM: bugs: hook processor bug checking into SMP and suspend paths
From: Florian Fainelli @ 2018-05-16 16:23 UTC (permalink / raw)
To: linux-arm-kernel
In-Reply-To: <E1fIuAu-0003jr-Kc@rmk-PC.armlinux.org.uk>
On 05/16/2018 04:00 AM, Russell King wrote:
> Check for CPU bugs when secondary processors are being brought online,
> and also when CPUs are resuming from a low power mode. This gives an
> opportunity to check that processor specific bug workarounds are
> correctly enabled for all paths that a CPU re-enters the kernel.
>
> Signed-off-by: Russell King <rmk+kernel@armlinux.org.uk>
Reviewed-by: Florian Fainelli <f.fainelli@gmail.com>
> ---
> arch/arm/include/asm/bugs.h | 2 ++
> arch/arm/kernel/bugs.c | 5 +++++
> arch/arm/kernel/smp.c | 4 ++++
> arch/arm/kernel/suspend.c | 2 ++
> 4 files changed, 13 insertions(+)
>
> diff --git a/arch/arm/include/asm/bugs.h b/arch/arm/include/asm/bugs.h
> index ed122d294f3f..73a99c72a930 100644
> --- a/arch/arm/include/asm/bugs.h
> +++ b/arch/arm/include/asm/bugs.h
> @@ -14,8 +14,10 @@ extern void check_writebuffer_bugs(void);
>
> #ifdef CONFIG_MMU
> extern void check_bugs(void);
> +extern void check_other_bugs(void);
> #else
> #define check_bugs() do { } while (0)
> +#define check_other_bugs() do { } while (0)
> #endif
>
> #endif
> diff --git a/arch/arm/kernel/bugs.c b/arch/arm/kernel/bugs.c
> index 88024028bb70..16e7ba2a9cc4 100644
> --- a/arch/arm/kernel/bugs.c
> +++ b/arch/arm/kernel/bugs.c
> @@ -3,7 +3,12 @@
> #include <asm/bugs.h>
> #include <asm/proc-fns.h>
>
> +void check_other_bugs(void)
> +{
> +}
> +
> void __init check_bugs(void)
> {
> check_writebuffer_bugs();
> + check_other_bugs();
> }
> diff --git a/arch/arm/kernel/smp.c b/arch/arm/kernel/smp.c
> index 2da087926ebe..5ad0b67b9e33 100644
> --- a/arch/arm/kernel/smp.c
> +++ b/arch/arm/kernel/smp.c
> @@ -31,6 +31,7 @@
> #include <linux/irq_work.h>
>
> #include <linux/atomic.h>
> +#include <asm/bugs.h>
> #include <asm/smp.h>
> #include <asm/cacheflush.h>
> #include <asm/cpu.h>
> @@ -405,6 +406,9 @@ asmlinkage void secondary_start_kernel(void)
> * before we continue - which happens after __cpu_up returns.
> */
> set_cpu_online(cpu, true);
> +
> + check_other_bugs();
Given what is currently implemented, I don't think the location of
check_other_bugs() matters too much, but we might have to move this
after the local_irq_enable() at some point if we need to check for e.g:
a bogus local timer or whatever?
--
Florian
^ permalink raw reply
* [PATCH 04/14] ARM: bugs: add support for per-processor bug checking
From: Florian Fainelli @ 2018-05-16 16:23 UTC (permalink / raw)
To: linux-arm-kernel
In-Reply-To: <E1fIuAz-0003k5-OP@rmk-PC.armlinux.org.uk>
On 05/16/2018 04:00 AM, Russell King wrote:
> Add support for per-processor bug checking - each processor function
> descriptor gains a function pointer for this check, which must not be
> an __init function. If non-NULL, this will be called whenever a CPU
> enters the kernel via which ever path (boot CPU, secondary CPU startup,
> CPU resuming, etc.)
>
> This allows processor specific bug checks to validate that workaround
> bits are properly enabled by firmware via all entry paths to the kernel.
>
> Signed-off-by: Russell King <rmk+kernel@armlinux.org.uk>
Reviewed-by: Florian Fainelli <f.fainelli@gmail.com>
--
Florian
^ permalink raw reply
* [PATCH 05/14] ARM: spectre: add Kconfig symbol for CPUs vulnerable to Spectre
From: Florian Fainelli @ 2018-05-16 16:24 UTC (permalink / raw)
To: linux-arm-kernel
In-Reply-To: <E1fIuB4-0003pm-SW@rmk-PC.armlinux.org.uk>
On 05/16/2018 04:01 AM, Russell King wrote:
> Add a Kconfig symbol for CPUs which are vulnerable to the Spectre
> attacks.
>
> Signed-off-by: Russell King <rmk+kernel@armlinux.org.uk>
Reviewed-by: Florian Fainelli <f.fainelli@gmail.com>
--
Florian
^ permalink raw reply
* [PATCH 2/2] ARM64: dts: meson-axg: remove incorrect i2c ao clock
From: Jerome Brunet @ 2018-05-16 16:27 UTC (permalink / raw)
To: linux-arm-kernel
In-Reply-To: <20180516155203.24922-3-jbrunet@baylibre.com>
On Wed, 2018-05-16 at 17:52 +0200, Jerome Brunet wrote:
> The clock specified for the i2c AO controller is the one from
> the EE domain, which is incorrect as this controller needs the
> clock from AO clock controller.
>
> Replace with xtal to avoid claiming an unrelated gate until the
> required clock controller becomes available.
I wrongly assumed the gate was provided by the clkc from AO domain.
Looking at what we did for the GX series, it seems to be provided by the EE
controller with CLKID_AO_I2C.
I'll send a v2 fixing this.
Sorry for the noise
>
> Fixes: dc6f858e2690 ("ARM64: dts: meson-axg: add I2C DT info for Meson-AXG SoC")
> Signed-off-by: Jerome Brunet <jbrunet@baylibre.com>
> ---
> arch/arm64/boot/dts/amlogic/meson-axg.dtsi | 7 ++++++-
> 1 file changed, 6 insertions(+), 1 deletion(-)
>
> diff --git a/arch/arm64/boot/dts/amlogic/meson-axg.dtsi b/arch/arm64/boot/dts/amlogic/meson-axg.dtsi
> index b59f341104d7..b82b9d79ec49 100644
> --- a/arch/arm64/boot/dts/amlogic/meson-axg.dtsi
> +++ b/arch/arm64/boot/dts/amlogic/meson-axg.dtsi
> @@ -1110,7 +1110,12 @@
> compatible = "amlogic,meson-axg-i2c";
> reg = <0x0 0x05000 0x0 0x20>;
> interrupts = <GIC_SPI 195 IRQ_TYPE_EDGE_RISING>;
> - clocks = <&clkc CLKID_I2C>;
> +
> + /*
> + * FIXME: replace with the correct clock when
> + * the controller is available
> + */
> + clocks = <&xtal>;
> #address-cells = <1>;
> #size-cells = <0>;
> status = "disabled";
^ permalink raw reply
* [PATCH 12/14] ARM: spectre-v2: KVM: invalidate icache on guest exit for Brahma B15
From: Florian Fainelli @ 2018-05-16 16:27 UTC (permalink / raw)
To: linux-arm-kernel
In-Reply-To: <E1fIuBe-0003qZ-PD@rmk-PC.armlinux.org.uk>
On 05/16/2018 04:01 AM, Russell King wrote:
> Include Brahma B15 in the Spectre v2 KVM workarounds.
>
> Signed-off-by: Russell King <rmk+kernel@armlinux.org.uk>
Reviewed-by: Florian Fainelli <f.fainelli@gmail.com>
--
Florian
^ permalink raw reply
* [PATCH v4 00/12] APEI in_nmi() rework and arm64 SDEI wire-up
From: James Morse @ 2018-05-16 16:28 UTC (permalink / raw)
To: linux-arm-kernel
The aim of this series is to wire arm64's SDEI into APEI.
Since v3 the NMI fixmap entries and locks have moved into their own
structure. This moves the indirection up from the 'lock', which should
be more acceptible to polite society.
Changes are noted in each patch.
This touches a few trees, so I'm not sure how best it should be merged.
Patches 11 and 12 are reducing a race that is made worse by patch 4, I'd
like them to arrive together, even though patch 11 doesn't depend on anything
else in the series. A partial merge of this would be 1-3 and 11.
The earlier boiler-plate:
What's SDEI? Its ARM's "Software Delegated Exception Interface" [0]. It's
used by firmware to tell the OS about firmware-first RAS events.
These Software exceptions can interrupt anything, so I describe them as
NMI-like. They aren't the only NMI-like way to notify the OS about
firmware-first RAS events, the ACPI spec also defines 'NOTFIY_SEA' and
'NOTIFY_SEI'.
(Acronyms: SEA, Synchronous External Abort. The CPU requested some memory,
but the owner of that memory said no. These are always synchronous with the
instruction that caused them. SEI, System-Error Interrupt, commonly called
SError. This is an asynchronous external abort, the memory-owner didn't say no
at the right point. Collectively these things are called external-aborts
How is firmware involved? It traps these and re-injects them into the kernel
once its written the CPER records).
APEI's GHES code only expects one source of NMI. If a platform implements
more than one of these mechanisms, APEI needs to handle the interaction.
'SEA' and 'SEI' can interact as 'SEI' is asynchronous. SDEI can interact
with itself: its exceptions can be 'normal' or 'critical', and firmware
could use both types for RAS. (errors using normal, 'panic-now' using
critical).
What does this series do?
Patches 1-4 refactor APEIs 'estatus queue' so it can be used for all
NMI-like notifications. This defers the NMI work to irq_work, which will
happen when we next unmask interrupts.
Patches 5&6 move the arch and KVM code around so that NMI-like notifications
are always called in_nmi().
Patch 7 changes the 'irq or nmi?' path through ghes_copy_tofrom_phys()
to be per-ghes. When called in_nmi(), the struct ghes is expected to
provide a fixmap slot and lock that is safe to use. NMI-like notifications
that mask each other can share these resources. Those that interact should
have their own fixmap slot and lock.
Patch 8 renames NOTIFY_SEA's use of NOTIFY_NMI's infrastructure, as we're
about to have multiple NMI-like users that can't share resources.
Pathes 9&10 add the SDEI helper, and notify methods for APEI.
After this, adding further firmware-first pieces for arm64 is simple
(and safe), and all our NMI-like notifications behave the same as x86's
NOTIFY_NMI.
All of this makes the race between memory_failure_queue() and
ret_to_user worse, as there is now always irq_work involved.
Patch 11 makes the reschedule to memory_failure() run as soon as possible.
Patch 12 makes sure the arch code knows whether the irq_work has run by
the time do_sea() returns. We can skip the signalling step if it has as
APEI has done its work.
ghes.c became clearer to me when I worked out that it has three sets of
functions with 'estatus' in the name. One is a pool of memory that can be
allocated-from atomically. This is grown/shrunk when new NMI users are
allocated.
The second is the estatus-cache, which holds recent notifications so it
can suppress notifications we've already handled.
The last it the estatus-queue, which holds data from NMI-like notifications
(in pool memory) to be processed from irq_work.
Testing?
Tested with the SDEI FVP based software model and a mocked up NOTFIY_SEA using
KVM. I've added a case where 'corrected errors' are discovered at probe time
to exercise ghes_probe() during boot. I've only build tested this on x86.
Thanks,
James
[0] http://infocenter.arm.com/help/topic/com.arm.doc.den0054a/ARM_DEN0054A_Software_Delegated_Exception_Interface.pdf
James Morse (12):
ACPI / APEI: Move the estatus queue code up, and under its own ifdef
ACPI / APEI: Generalise the estatus queue's add/remove and notify code
ACPI / APEI: don't wait to serialise with oops messages when
panic()ing
ACPI / APEI: Switch NOTIFY_SEA to use the estatus queue
KVM: arm/arm64: Add kvm_ras.h to collect kvm specific RAS plumbing
arm64: KVM/mm: Move SEA handling behind a single 'claim' interface
ACPI / APEI: Make the nmi_fixmap_idx per-ghes to allow multiple
in_nmi() users
ACPI / APEI: Split fixmap pages for arm64 NMI-like notifications
firmware: arm_sdei: Add ACPI GHES registration helper
ACPI / APEI: Add support for the SDEI GHES Notification type
mm/memory-failure: increase queued recovery work's priority
arm64: acpi: Make apei_claim_sea() synchronise with APEI's irq work
arch/arm/include/asm/kvm_ras.h | 14 +
arch/arm/include/asm/system_misc.h | 5 -
arch/arm64/include/asm/acpi.h | 4 +
arch/arm64/include/asm/daifflags.h | 1 +
arch/arm64/include/asm/fixmap.h | 8 +-
arch/arm64/include/asm/kvm_ras.h | 24 ++
arch/arm64/include/asm/system_misc.h | 2 -
arch/arm64/kernel/acpi.c | 49 ++++
arch/arm64/mm/fault.c | 30 +-
drivers/acpi/apei/ghes.c | 518 ++++++++++++++++++++---------------
drivers/firmware/arm_sdei.c | 67 +++++
include/acpi/ghes.h | 17 ++
include/linux/arm_sdei.h | 8 +
mm/memory-failure.c | 11 +-
virt/kvm/arm/mmu.c | 4 +-
15 files changed, 503 insertions(+), 259 deletions(-)
create mode 100644 arch/arm/include/asm/kvm_ras.h
create mode 100644 arch/arm64/include/asm/kvm_ras.h
--
2.16.2
^ permalink raw reply
* [PATCH v4 01/12] ACPI / APEI: Move the estatus queue code up, and under its own ifdef
From: James Morse @ 2018-05-16 16:28 UTC (permalink / raw)
To: linux-arm-kernel
In-Reply-To: <20180516162829.14348-1-james.morse@arm.com>
To support asynchronous NMI-like notifications on arm64 we need to use
the estatus-queue. These patches refactor it to allow multiple APEI
notification types to use it.
First we move the estatus-queue code higher in the file so that any
notify_foo() handler can make use of it.
This patch moves code around ... and makes the following trivial change:
Freshen the dated comment above ghes_estatus_llist. printk() is no
longer the issue, its the helpers like memory_failure_queue() that
still aren't nmi safe.
Signed-off-by: James Morse <james.morse@arm.com>
Reviewed-by: Punit Agrawal <punit.agrawal@arm.com>
Reviewed-by: Borislav Petkov <bp@suse.de>
Tested-by: Tyler Baicar <tbaicar@codeaurora.org>
---
drivers/acpi/apei/ghes.c | 265 ++++++++++++++++++++++++-----------------------
1 file changed, 137 insertions(+), 128 deletions(-)
diff --git a/drivers/acpi/apei/ghes.c b/drivers/acpi/apei/ghes.c
index 1efefe919555..e2af91c92135 100644
--- a/drivers/acpi/apei/ghes.c
+++ b/drivers/acpi/apei/ghes.c
@@ -545,6 +545,16 @@ static int ghes_print_estatus(const char *pfx,
return 0;
}
+static void __ghes_panic(struct ghes *ghes)
+{
+ __ghes_print_estatus(KERN_EMERG, ghes->generic, ghes->estatus);
+
+ /* reboot to log the error! */
+ if (!panic_timeout)
+ panic_timeout = ghes_panic_timeout;
+ panic("Fatal hardware error!");
+}
+
/*
* GHES error status reporting throttle, to report more kinds of
* errors, instead of just most frequently occurred errors.
@@ -672,6 +682,133 @@ static void ghes_estatus_cache_add(
rcu_read_unlock();
}
+#ifdef CONFIG_HAVE_ACPI_APEI_NMI
+/*
+ * Handlers for CPER records may not be NMI safe. For example,
+ * memory_failure_queue() takes spinlocks and calls schedule_work_on().
+ * In any NMI-like handler, memory from ghes_estatus_pool is used to save
+ * estatus, and added to the ghes_estatus_llist. irq_work_queue() causes
+ * ghes_proc_in_irq() to run in IRQ context where each estatus in
+ * ghes_estatus_llist is processed. Each NMI-like error source must grow
+ * the ghes_estatus_pool to ensure memory is available.
+ *
+ * Memory from the ghes_estatus_pool is also used with the ghes_estatus_cache
+ * to suppress frequent messages.
+ */
+static struct llist_head ghes_estatus_llist;
+static struct irq_work ghes_proc_irq_work;
+
+static void ghes_print_queued_estatus(void)
+{
+ struct llist_node *llnode;
+ struct ghes_estatus_node *estatus_node;
+ struct acpi_hest_generic *generic;
+ struct acpi_hest_generic_status *estatus;
+
+ llnode = llist_del_all(&ghes_estatus_llist);
+ /*
+ * Because the time order of estatus in list is reversed,
+ * revert it back to proper order.
+ */
+ llnode = llist_reverse_order(llnode);
+ while (llnode) {
+ estatus_node = llist_entry(llnode, struct ghes_estatus_node,
+ llnode);
+ estatus = GHES_ESTATUS_FROM_NODE(estatus_node);
+ generic = estatus_node->generic;
+ ghes_print_estatus(NULL, generic, estatus);
+ llnode = llnode->next;
+ }
+}
+
+/* Save estatus for further processing in IRQ context */
+static void __process_error(struct ghes *ghes)
+{
+#ifdef CONFIG_ARCH_HAVE_NMI_SAFE_CMPXCHG
+ u32 len, node_len;
+ struct ghes_estatus_node *estatus_node;
+ struct acpi_hest_generic_status *estatus;
+
+ if (ghes_estatus_cached(ghes->estatus))
+ return;
+
+ len = cper_estatus_len(ghes->estatus);
+ node_len = GHES_ESTATUS_NODE_LEN(len);
+
+ estatus_node = (void *)gen_pool_alloc(ghes_estatus_pool, node_len);
+ if (!estatus_node)
+ return;
+
+ estatus_node->ghes = ghes;
+ estatus_node->generic = ghes->generic;
+ estatus = GHES_ESTATUS_FROM_NODE(estatus_node);
+ memcpy(estatus, ghes->estatus, len);
+ llist_add(&estatus_node->llnode, &ghes_estatus_llist);
+#endif
+}
+
+static unsigned long ghes_esource_prealloc_size(
+ const struct acpi_hest_generic *generic)
+{
+ unsigned long block_length, prealloc_records, prealloc_size;
+
+ block_length = min_t(unsigned long, generic->error_block_length,
+ GHES_ESTATUS_MAX_SIZE);
+ prealloc_records = max_t(unsigned long,
+ generic->records_to_preallocate, 1);
+ prealloc_size = min_t(unsigned long, block_length * prealloc_records,
+ GHES_ESOURCE_PREALLOC_MAX_SIZE);
+
+ return prealloc_size;
+}
+
+static void ghes_estatus_pool_shrink(unsigned long len)
+{
+ ghes_estatus_pool_size_request -= PAGE_ALIGN(len);
+}
+
+static void ghes_proc_in_irq(struct irq_work *irq_work)
+{
+ struct llist_node *llnode, *next;
+ struct ghes_estatus_node *estatus_node;
+ struct acpi_hest_generic *generic;
+ struct acpi_hest_generic_status *estatus;
+ u32 len, node_len;
+
+ llnode = llist_del_all(&ghes_estatus_llist);
+ /*
+ * Because the time order of estatus in list is reversed,
+ * revert it back to proper order.
+ */
+ llnode = llist_reverse_order(llnode);
+ while (llnode) {
+ next = llnode->next;
+ estatus_node = llist_entry(llnode, struct ghes_estatus_node,
+ llnode);
+ estatus = GHES_ESTATUS_FROM_NODE(estatus_node);
+ len = cper_estatus_len(estatus);
+ node_len = GHES_ESTATUS_NODE_LEN(len);
+ ghes_do_proc(estatus_node->ghes, estatus);
+ if (!ghes_estatus_cached(estatus)) {
+ generic = estatus_node->generic;
+ if (ghes_print_estatus(NULL, generic, estatus))
+ ghes_estatus_cache_add(generic, estatus);
+ }
+ gen_pool_free(ghes_estatus_pool, (unsigned long)estatus_node,
+ node_len);
+ llnode = next;
+ }
+}
+
+static void ghes_nmi_init_cxt(void)
+{
+ init_irq_work(&ghes_proc_irq_work, ghes_proc_in_irq);
+}
+
+#else
+static inline void ghes_nmi_init_cxt(void) { }
+#endif /* CONFIG_HAVE_ACPI_APEI_NMI */
+
static int ghes_ack_error(struct acpi_hest_generic_v2 *gv2)
{
int rc;
@@ -687,16 +824,6 @@ static int ghes_ack_error(struct acpi_hest_generic_v2 *gv2)
return apei_write(val, &gv2->read_ack_register);
}
-static void __ghes_panic(struct ghes *ghes)
-{
- __ghes_print_estatus(KERN_EMERG, ghes->generic, ghes->estatus);
-
- /* reboot to log the error! */
- if (!panic_timeout)
- panic_timeout = ghes_panic_timeout;
- panic("Fatal hardware error!");
-}
-
static int ghes_proc(struct ghes *ghes)
{
int rc;
@@ -828,17 +955,6 @@ static inline void ghes_sea_remove(struct ghes *ghes) { }
#endif /* CONFIG_ACPI_APEI_SEA */
#ifdef CONFIG_HAVE_ACPI_APEI_NMI
-/*
- * printk is not safe in NMI context. So in NMI handler, we allocate
- * required memory from lock-less memory allocator
- * (ghes_estatus_pool), save estatus into it, put them into lock-less
- * list (ghes_estatus_llist), then delay printk into IRQ context via
- * irq_work (ghes_proc_irq_work). ghes_estatus_size_request record
- * required pool size by all NMI error source.
- */
-static struct llist_head ghes_estatus_llist;
-static struct irq_work ghes_proc_irq_work;
-
/*
* NMI may be triggered on any CPU, so ghes_in_nmi is used for
* having only one concurrent reader.
@@ -847,88 +963,6 @@ static atomic_t ghes_in_nmi = ATOMIC_INIT(0);
static LIST_HEAD(ghes_nmi);
-static void ghes_proc_in_irq(struct irq_work *irq_work)
-{
- struct llist_node *llnode, *next;
- struct ghes_estatus_node *estatus_node;
- struct acpi_hest_generic *generic;
- struct acpi_hest_generic_status *estatus;
- u32 len, node_len;
-
- llnode = llist_del_all(&ghes_estatus_llist);
- /*
- * Because the time order of estatus in list is reversed,
- * revert it back to proper order.
- */
- llnode = llist_reverse_order(llnode);
- while (llnode) {
- next = llnode->next;
- estatus_node = llist_entry(llnode, struct ghes_estatus_node,
- llnode);
- estatus = GHES_ESTATUS_FROM_NODE(estatus_node);
- len = cper_estatus_len(estatus);
- node_len = GHES_ESTATUS_NODE_LEN(len);
- ghes_do_proc(estatus_node->ghes, estatus);
- if (!ghes_estatus_cached(estatus)) {
- generic = estatus_node->generic;
- if (ghes_print_estatus(NULL, generic, estatus))
- ghes_estatus_cache_add(generic, estatus);
- }
- gen_pool_free(ghes_estatus_pool, (unsigned long)estatus_node,
- node_len);
- llnode = next;
- }
-}
-
-static void ghes_print_queued_estatus(void)
-{
- struct llist_node *llnode;
- struct ghes_estatus_node *estatus_node;
- struct acpi_hest_generic *generic;
- struct acpi_hest_generic_status *estatus;
-
- llnode = llist_del_all(&ghes_estatus_llist);
- /*
- * Because the time order of estatus in list is reversed,
- * revert it back to proper order.
- */
- llnode = llist_reverse_order(llnode);
- while (llnode) {
- estatus_node = llist_entry(llnode, struct ghes_estatus_node,
- llnode);
- estatus = GHES_ESTATUS_FROM_NODE(estatus_node);
- generic = estatus_node->generic;
- ghes_print_estatus(NULL, generic, estatus);
- llnode = llnode->next;
- }
-}
-
-/* Save estatus for further processing in IRQ context */
-static void __process_error(struct ghes *ghes)
-{
-#ifdef CONFIG_ARCH_HAVE_NMI_SAFE_CMPXCHG
- u32 len, node_len;
- struct ghes_estatus_node *estatus_node;
- struct acpi_hest_generic_status *estatus;
-
- if (ghes_estatus_cached(ghes->estatus))
- return;
-
- len = cper_estatus_len(ghes->estatus);
- node_len = GHES_ESTATUS_NODE_LEN(len);
-
- estatus_node = (void *)gen_pool_alloc(ghes_estatus_pool, node_len);
- if (!estatus_node)
- return;
-
- estatus_node->ghes = ghes;
- estatus_node->generic = ghes->generic;
- estatus = GHES_ESTATUS_FROM_NODE(estatus_node);
- memcpy(estatus, ghes->estatus, len);
- llist_add(&estatus_node->llnode, &ghes_estatus_llist);
-#endif
-}
-
static int ghes_notify_nmi(unsigned int cmd, struct pt_regs *regs)
{
struct ghes *ghes;
@@ -967,26 +1001,6 @@ static int ghes_notify_nmi(unsigned int cmd, struct pt_regs *regs)
return ret;
}
-static unsigned long ghes_esource_prealloc_size(
- const struct acpi_hest_generic *generic)
-{
- unsigned long block_length, prealloc_records, prealloc_size;
-
- block_length = min_t(unsigned long, generic->error_block_length,
- GHES_ESTATUS_MAX_SIZE);
- prealloc_records = max_t(unsigned long,
- generic->records_to_preallocate, 1);
- prealloc_size = min_t(unsigned long, block_length * prealloc_records,
- GHES_ESOURCE_PREALLOC_MAX_SIZE);
-
- return prealloc_size;
-}
-
-static void ghes_estatus_pool_shrink(unsigned long len)
-{
- ghes_estatus_pool_size_request -= PAGE_ALIGN(len);
-}
-
static void ghes_nmi_add(struct ghes *ghes)
{
unsigned long len;
@@ -1018,14 +1032,9 @@ static void ghes_nmi_remove(struct ghes *ghes)
ghes_estatus_pool_shrink(len);
}
-static void ghes_nmi_init_cxt(void)
-{
- init_irq_work(&ghes_proc_irq_work, ghes_proc_in_irq);
-}
#else /* CONFIG_HAVE_ACPI_APEI_NMI */
static inline void ghes_nmi_add(struct ghes *ghes) { }
static inline void ghes_nmi_remove(struct ghes *ghes) { }
-static inline void ghes_nmi_init_cxt(void) { }
#endif /* CONFIG_HAVE_ACPI_APEI_NMI */
static int ghes_probe(struct platform_device *ghes_dev)
--
2.16.2
^ permalink raw reply related
* [PATCH v4 02/12] ACPI / APEI: Generalise the estatus queue's add/remove and notify code
From: James Morse @ 2018-05-16 16:28 UTC (permalink / raw)
To: linux-arm-kernel
In-Reply-To: <20180516162829.14348-1-james.morse@arm.com>
Refactor the estatus queue's pool grow/shrink code and notification
routine from NOTIFY_NMI's handlers. This will allow another notification
method to use the estatus queue without duplicating this code.
This patch adds rcu_read_lock()/rcu_read_unlock() around the list
list_for_each_entry_rcu() walker. These aren't strictly necessary as
the whole nmi_enter/nmi_exit() window is a spooky RCU read-side
critical section.
The existing ghes_estatus_pool_shrink() is folded into the new
ghes_estatus_queue_shrink_pool() as only the queue uses it.
_in_nmi_notify_one() is separate from the rcu-list walker for a later
caller that doesn't need to walk a list.
Signed-off-by: James Morse <james.morse@arm.com>
Reviewed-by: Punit Agrawal <punit.agrawal@arm.com>
Tested-by: Tyler Baicar <tbaicar@codeaurora.org>
---
Changes since v3:
* Removed dupicate or redundant paragraphs in commit message.
* Fixed the style of a zero check
Changes since v1:
* Tidied up _in_nmi_notify_one().
drivers/acpi/apei/ghes.c | 100 ++++++++++++++++++++++++++++++-----------------
1 file changed, 65 insertions(+), 35 deletions(-)
diff --git a/drivers/acpi/apei/ghes.c b/drivers/acpi/apei/ghes.c
index e2af91c92135..5a0b8a1bddb1 100644
--- a/drivers/acpi/apei/ghes.c
+++ b/drivers/acpi/apei/ghes.c
@@ -747,6 +747,51 @@ static void __process_error(struct ghes *ghes)
#endif
}
+static int _in_nmi_notify_one(struct ghes *ghes)
+{
+ int sev;
+
+ if (ghes_read_estatus(ghes, 1)) {
+ ghes_clear_estatus(ghes);
+ return -ENOENT;
+ }
+
+ sev = ghes_severity(ghes->estatus->error_severity);
+ if (sev >= GHES_SEV_PANIC) {
+#ifdef CONFIG_X86
+ oops_begin();
+#endif
+ ghes_print_queued_estatus();
+ __ghes_panic(ghes);
+ }
+
+ if (!(ghes->flags & GHES_TO_CLEAR))
+ return 0;
+
+ __process_error(ghes);
+ ghes_clear_estatus(ghes);
+
+ return 0;
+}
+
+static int ghes_estatus_queue_notified(struct list_head *rcu_list)
+{
+ int ret = -ENOENT;
+ struct ghes *ghes;
+
+ rcu_read_lock();
+ list_for_each_entry_rcu(ghes, rcu_list, list) {
+ if (!_in_nmi_notify_one(ghes))
+ ret = 0;
+ }
+ rcu_read_unlock();
+
+ if (IS_ENABLED(CONFIG_ARCH_HAVE_NMI_SAFE_CMPXCHG) && !ret)
+ irq_work_queue(&ghes_proc_irq_work);
+
+ return ret;
+}
+
static unsigned long ghes_esource_prealloc_size(
const struct acpi_hest_generic *generic)
{
@@ -762,11 +807,24 @@ static unsigned long ghes_esource_prealloc_size(
return prealloc_size;
}
-static void ghes_estatus_pool_shrink(unsigned long len)
+/* After removing a queue user, we can shrink the pool */
+static void ghes_estatus_queue_shrink_pool(struct ghes *ghes)
{
+ unsigned long len;
+
+ len = ghes_esource_prealloc_size(ghes->generic);
ghes_estatus_pool_size_request -= PAGE_ALIGN(len);
}
+/* Before adding a queue user, grow the pool */
+static void ghes_estatus_queue_grow_pool(struct ghes *ghes)
+{
+ unsigned long len;
+
+ len = ghes_esource_prealloc_size(ghes->generic);
+ ghes_estatus_pool_expand(len);
+}
+
static void ghes_proc_in_irq(struct irq_work *irq_work)
{
struct llist_node *llnode, *next;
@@ -965,48 +1023,22 @@ static LIST_HEAD(ghes_nmi);
static int ghes_notify_nmi(unsigned int cmd, struct pt_regs *regs)
{
- struct ghes *ghes;
- int sev, ret = NMI_DONE;
+ int ret = NMI_DONE;
if (!atomic_add_unless(&ghes_in_nmi, 1, 1))
return ret;
- list_for_each_entry_rcu(ghes, &ghes_nmi, list) {
- if (ghes_read_estatus(ghes, 1)) {
- ghes_clear_estatus(ghes);
- continue;
- } else {
- ret = NMI_HANDLED;
- }
-
- sev = ghes_severity(ghes->estatus->error_severity);
- if (sev >= GHES_SEV_PANIC) {
- oops_begin();
- ghes_print_queued_estatus();
- __ghes_panic(ghes);
- }
+ if (!ghes_estatus_queue_notified(&ghes_nmi))
+ ret = NMI_HANDLED;
- if (!(ghes->flags & GHES_TO_CLEAR))
- continue;
-
- __process_error(ghes);
- ghes_clear_estatus(ghes);
- }
-
-#ifdef CONFIG_ARCH_HAVE_NMI_SAFE_CMPXCHG
- if (ret == NMI_HANDLED)
- irq_work_queue(&ghes_proc_irq_work);
-#endif
atomic_dec(&ghes_in_nmi);
return ret;
}
static void ghes_nmi_add(struct ghes *ghes)
{
- unsigned long len;
+ ghes_estatus_queue_grow_pool(ghes);
- len = ghes_esource_prealloc_size(ghes->generic);
- ghes_estatus_pool_expand(len);
mutex_lock(&ghes_list_mutex);
if (list_empty(&ghes_nmi))
register_nmi_handler(NMI_LOCAL, ghes_notify_nmi, 0, "ghes");
@@ -1016,8 +1048,6 @@ static void ghes_nmi_add(struct ghes *ghes)
static void ghes_nmi_remove(struct ghes *ghes)
{
- unsigned long len;
-
mutex_lock(&ghes_list_mutex);
list_del_rcu(&ghes->list);
if (list_empty(&ghes_nmi))
@@ -1028,8 +1058,8 @@ static void ghes_nmi_remove(struct ghes *ghes)
* freed after NMI handler finishes.
*/
synchronize_rcu();
- len = ghes_esource_prealloc_size(ghes->generic);
- ghes_estatus_pool_shrink(len);
+
+ ghes_estatus_queue_shrink_pool(ghes);
}
#else /* CONFIG_HAVE_ACPI_APEI_NMI */
--
2.16.2
^ permalink raw reply related
page: next (older) | prev (newer) | latest
- recent:[subjects (threaded)|topics (new)|topics (active)]
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox