* [PATCH v2 2/2] dt-bindings: thermal: tsens: Add zeroc interrupt support in yaml
From: Manaf Meethalavalappu Pallikunhi @ 2020-05-17 10:46 UTC (permalink / raw)
To: Amit Kucheria, Andy Gross, Bjorn Andersson, Zhang Rui,
Daniel Lezcano, Rob Herring
Cc: linux-pm, linux-arm-msm, devicetree, linux-kernel,
Manaf Meethalavalappu Pallikunhi
In-Reply-To: <20200517104627.29501-1-manafm@codeaurora.org>
Add zeroc interrupt support for tsens in yaml.
Signed-off-by: Manaf Meethalavalappu Pallikunhi <manafm@codeaurora.org>
---
.../bindings/thermal/qcom-tsens.yaml | 21 +++++++++++++++++++
1 file changed, 21 insertions(+)
diff --git a/Documentation/devicetree/bindings/thermal/qcom-tsens.yaml b/Documentation/devicetree/bindings/thermal/qcom-tsens.yaml
index 2ddd39d96766..717b0dd967e2 100644
--- a/Documentation/devicetree/bindings/thermal/qcom-tsens.yaml
+++ b/Documentation/devicetree/bindings/thermal/qcom-tsens.yaml
@@ -52,12 +52,14 @@ properties:
items:
- description: Combined interrupt if upper or lower threshold crossed
- description: Interrupt if critical threshold crossed
+ - description: Interrupt if zeroC threshold is crossed
interrupt-names:
minItems: 1
items:
- const: uplow
- const: critical
+ - const: zeroc
nvmem-cells:
minItems: 1
@@ -109,8 +111,10 @@ allOf:
properties:
interrupts:
minItems: 2
+ maxItems: 3
interrupt-names:
minItems: 2
+ maxItems: 3
required:
- compatible
@@ -174,4 +178,21 @@ examples:
#qcom,sensors = <13>;
#thermal-sensor-cells = <1>;
};
+
+ - |
+ #include <dt-bindings/interrupt-controller/arm-gic.h>
+ // Example 4 (for any platform containing v2.6+ of the TSENS IP):
+ tsens4: thermal-sensor@c265000 {
+ compatible = "qcom,sc7180-tsens", "qcom,tsens-v2";
+ reg = <0xc265000 0x1ff>,
+ <0xc223000 0x1ff>;
+
+ interrupts = <GIC_SPI 507 IRQ_TYPE_LEVEL_HIGH>,
+ <GIC_SPI 509 IRQ_TYPE_LEVEL_HIGH>,
+ <GIC_SPI 501 IRQ_TYPE_EDGE_RISING>;
+ interrupt-names = "uplow", "critical", "zeroc";
+
+ #qcom,sensors = <15>;
+ #thermal-sensor-cells = <1>;
+ };
...
--
2.26.2
^ permalink raw reply related
* [PATCH v2 1/2] drivers: thermal: tsens: Add zeroc interrupt support
From: Manaf Meethalavalappu Pallikunhi @ 2020-05-17 10:46 UTC (permalink / raw)
To: Amit Kucheria, Andy Gross, Bjorn Andersson, Zhang Rui,
Daniel Lezcano, Rob Herring
Cc: linux-pm, linux-arm-msm, devicetree, linux-kernel,
Manaf Meethalavalappu Pallikunhi
In-Reply-To: <20200517104627.29501-1-manafm@codeaurora.org>
TSENS IP v2.6+ adds zeroc interrupt support. It triggers set
interrupt when aggregated minimum temperature of all TSENS falls
below zeroc preset threshold and triggers reset interrupt when
aggregated minimum temperature of all TSENS crosses above reset
threshold. Add support for this interrupt in the driver.
It adds another sensor to the of-thermal along with all individual
TSENS. It enables to add any mitigation for zeroc interrupt.
Signed-off-by: Manaf Meethalavalappu Pallikunhi <manafm@codeaurora.org>
---
drivers/thermal/qcom/tsens-v2.c | 5 ++
drivers/thermal/qcom/tsens.c | 107 +++++++++++++++++++++++++++++++-
drivers/thermal/qcom/tsens.h | 10 +++
3 files changed, 120 insertions(+), 2 deletions(-)
diff --git a/drivers/thermal/qcom/tsens-v2.c b/drivers/thermal/qcom/tsens-v2.c
index b293ed32174b..8f30b859ab22 100644
--- a/drivers/thermal/qcom/tsens-v2.c
+++ b/drivers/thermal/qcom/tsens-v2.c
@@ -23,6 +23,7 @@
#define TM_Sn_UPPER_LOWER_THRESHOLD_OFF 0x0020
#define TM_Sn_CRITICAL_THRESHOLD_OFF 0x0060
#define TM_Sn_STATUS_OFF 0x00a0
+#define TM_ZEROC_INT_STATUS_OFF 0x00e0
#define TM_TRDY_OFF 0x00e4
#define TM_WDOG_LOG_OFF 0x013c
@@ -86,6 +87,9 @@ static const struct reg_field tsens_v2_regfields[MAX_REGFIELDS] = {
REG_FIELD_FOR_EACH_SENSOR16(CRITICAL_STATUS, TM_Sn_STATUS_OFF, 19, 19),
REG_FIELD_FOR_EACH_SENSOR16(MAX_STATUS, TM_Sn_STATUS_OFF, 20, 20),
+ /* ZEROC INTERRUPT STATUS */
+ [ZEROC_STATUS] = REG_FIELD(TM_ZEROC_INT_STATUS_OFF, 0, 0),
+
/* TRDY: 1=ready, 0=in progress */
[TRDY] = REG_FIELD(TM_TRDY_OFF, 0, 0),
};
@@ -93,6 +97,7 @@ static const struct reg_field tsens_v2_regfields[MAX_REGFIELDS] = {
static const struct tsens_ops ops_generic_v2 = {
.init = init_common,
.get_temp = get_temp_tsens_valid,
+ .get_zeroc_status = get_tsens_zeroc_status,
};
struct tsens_plat_data data_tsens_v2 = {
diff --git a/drivers/thermal/qcom/tsens.c b/drivers/thermal/qcom/tsens.c
index 8d3e94d2a9ed..dd0172f05eb6 100644
--- a/drivers/thermal/qcom/tsens.c
+++ b/drivers/thermal/qcom/tsens.c
@@ -205,7 +205,8 @@ static void tsens_set_interrupt_v1(struct tsens_priv *priv, u32 hw_id,
index = LOW_INT_CLEAR_0 + hw_id;
break;
case CRITICAL:
- /* No critical interrupts before v2 */
+ case ZEROC:
+ /* No critical and zeroc interrupts before v2 */
return;
}
regmap_field_write(priv->rf[index], enable ? 0 : 1);
@@ -236,6 +237,9 @@ static void tsens_set_interrupt_v2(struct tsens_priv *priv, u32 hw_id,
index_mask = CRIT_INT_MASK_0 + hw_id;
index_clear = CRIT_INT_CLEAR_0 + hw_id;
break;
+ case ZEROC:
+ /* Nothing to handle for zeroc interrupt */
+ return;
}
if (enable) {
@@ -367,6 +371,35 @@ static inline u32 masked_irq(u32 hw_id, u32 mask, enum tsens_ver ver)
return 0;
}
+/**
+ * tsens_zeroc_irq_thread - Threaded interrupt handler for zeroc interrupt
+ * @irq: irq number
+ * @data: tsens controller private data
+ *
+ * Whenever interrupt triggers notify thermal framework using
+ * thermal_zone_device_update().
+ *
+ * Return: IRQ_HANDLED
+ */
+
+irqreturn_t tsens_zeroc_irq_thread(int irq, void *data)
+{
+ struct tsens_priv *priv = data;
+ struct tsens_sensor *s = &priv->sensor[priv->num_sensors];
+ int temp, ret;
+
+ ret = regmap_field_read(priv->rf[ZEROC_STATUS], &temp);
+ if (ret)
+ return ret;
+
+ dev_dbg(priv->dev, "[%u] %s: zeroc interrupt is %s\n",
+ s->hw_id, __func__, temp ? "triggered" : "cleared");
+
+ thermal_zone_device_update(s->tzd, THERMAL_EVENT_UNSPECIFIED);
+
+ return IRQ_HANDLED;
+}
+
/**
* tsens_critical_irq_thread() - Threaded handler for critical interrupts
* @irq: irq number
@@ -575,6 +608,20 @@ void tsens_disable_irq(struct tsens_priv *priv)
regmap_field_write(priv->rf[INT_EN], 0);
}
+int get_tsens_zeroc_status(const struct tsens_sensor *s, int *temp)
+{
+ struct tsens_priv *priv = s->priv;
+ int last_temp = 0, ret;
+
+ ret = regmap_field_read(priv->rf[ZEROC_STATUS], &last_temp);
+ if (ret)
+ return ret;
+
+ *temp = last_temp;
+
+ return 0;
+}
+
int get_temp_tsens_valid(const struct tsens_sensor *s, int *temp)
{
struct tsens_priv *priv = s->priv;
@@ -843,6 +890,19 @@ int __init init_common(struct tsens_priv *priv)
regmap_field_write(priv->rf[CC_MON_MASK], 1);
}
+ if (tsens_version(priv) > VER_1_X && ver_minor > 5) {
+ /* ZEROC interrupt is present only on v2.6+ */
+ priv->feat->zeroc_int = 1;
+ priv->rf[ZEROC_STATUS] = devm_regmap_field_alloc(
+ dev,
+ priv->tm_map,
+ priv->fields[ZEROC_STATUS]);
+ if (IS_ERR(priv->rf[ZEROC_STATUS])) {
+ ret = PTR_ERR(priv->rf[ZEROC_STATUS]);
+ goto err_put_device;
+ }
+ }
+
spin_lock_init(&priv->ul_lock);
tsens_enable_irq(priv);
tsens_debug_init(op);
@@ -852,6 +912,17 @@ int __init init_common(struct tsens_priv *priv)
return ret;
}
+static int tsens_zeroc_get_temp(void *data, int *temp)
+{
+ struct tsens_sensor *s = data;
+ struct tsens_priv *priv = s->priv;
+
+ if (priv->ops->get_zeroc_status)
+ return priv->ops->get_zeroc_status(s, temp);
+
+ return -ENOTSUPP;
+}
+
static int tsens_get_temp(void *data, int *temp)
{
struct tsens_sensor *s = data;
@@ -923,6 +994,10 @@ static const struct thermal_zone_of_device_ops tsens_of_ops = {
.set_trips = tsens_set_trips,
};
+static const struct thermal_zone_of_device_ops tsens_zeroc_of_ops = {
+ .get_temp = tsens_zeroc_get_temp,
+};
+
static int tsens_register_irq(struct tsens_priv *priv, char *irqname,
irq_handler_t thread_fn)
{
@@ -980,6 +1055,21 @@ static int tsens_register(struct tsens_priv *priv)
ret = tsens_register_irq(priv, "critical",
tsens_critical_irq_thread);
+ if (priv->feat->zeroc_int && priv->zeroc_en) {
+ priv->sensor[priv->num_sensors].priv = priv;
+ tzd = devm_thermal_zone_of_sensor_register(priv->dev,
+ priv->sensor[priv->num_sensors].hw_id,
+ &priv->sensor[priv->num_sensors],
+ &tsens_zeroc_of_ops);
+ if (IS_ERR(tzd)) {
+ ret = 0;
+ return ret;
+ }
+
+ priv->sensor[priv->num_sensors].tzd = tzd;
+ ret = tsens_register_irq(priv, "zeroc", tsens_zeroc_irq_thread);
+ }
+
return ret;
}
@@ -992,6 +1082,7 @@ static int tsens_probe(struct platform_device *pdev)
const struct tsens_plat_data *data;
const struct of_device_id *id;
u32 num_sensors;
+ bool zeroc_en = false;
if (pdev->dev.of_node)
dev = &pdev->dev;
@@ -1016,6 +1107,12 @@ static int tsens_probe(struct platform_device *pdev)
return -EINVAL;
}
+ /* Check whether zeroc interrupt is enabled or not */
+ if (platform_get_irq_byname(pdev, "zeroc") > 0) {
+ zeroc_en = true;
+ num_sensors++;
+ }
+
priv = devm_kzalloc(dev,
struct_size(priv, sensor, num_sensors),
GFP_KERNEL);
@@ -1023,7 +1120,7 @@ static int tsens_probe(struct platform_device *pdev)
return -ENOMEM;
priv->dev = dev;
- priv->num_sensors = num_sensors;
+ priv->num_sensors = zeroc_en ? num_sensors - 1 : num_sensors;
priv->ops = data->ops;
for (i = 0; i < priv->num_sensors; i++) {
if (data->hw_ids)
@@ -1031,6 +1128,12 @@ static int tsens_probe(struct platform_device *pdev)
else
priv->sensor[i].hw_id = i;
}
+
+ if (zeroc_en) {
+ priv->zeroc_en = zeroc_en;
+ priv->sensor[num_sensors].hw_id = data->feat->max_sensors;
+ }
+
priv->feat = data->feat;
priv->fields = data->fields;
diff --git a/drivers/thermal/qcom/tsens.h b/drivers/thermal/qcom/tsens.h
index 59d01162c66a..34d24332b320 100644
--- a/drivers/thermal/qcom/tsens.h
+++ b/drivers/thermal/qcom/tsens.h
@@ -34,6 +34,7 @@ enum tsens_irq_type {
LOWER,
UPPER,
CRITICAL,
+ ZEROC,
};
/**
@@ -64,6 +65,7 @@ struct tsens_sensor {
* @suspend: Function to suspend the tsens device
* @resume: Function to resume the tsens device
* @get_trend: Function to get the thermal/temp trend
+ * @get_zeroc_status: Function to get the zeroc interrupt status
*/
struct tsens_ops {
/* mandatory callbacks */
@@ -76,6 +78,7 @@ struct tsens_ops {
int (*suspend)(struct tsens_priv *priv);
int (*resume)(struct tsens_priv *priv);
int (*get_trend)(struct tsens_sensor *s, enum thermal_trend *trend);
+ int (*get_zeroc_status)(const struct tsens_sensor *s, int *temp);
};
#define REG_FIELD_FOR_EACH_SENSOR11(_name, _offset, _startbit, _stopbit) \
@@ -485,6 +488,8 @@ enum regfield_ids {
MAX_STATUS_14,
MAX_STATUS_15,
+ ZEROC_STATUS, /* ZEROC INTERRUPT status */
+
/* Keep last */
MAX_REGFIELDS
};
@@ -497,6 +502,7 @@ enum regfield_ids {
* @srot_split: does the IP neatly splits the register space into SROT and TM,
* with SROT only being available to secure boot firmware?
* @has_watchdog: does this IP support watchdog functionality?
+ * @zeroc_int: does this IP support ZEROC interrupt ?
* @max_sensors: maximum sensors supported by this version of the IP
*/
struct tsens_features {
@@ -505,6 +511,7 @@ struct tsens_features {
unsigned int adc:1;
unsigned int srot_split:1;
unsigned int has_watchdog:1;
+ unsigned int zeroc_int:1;
unsigned int max_sensors;
};
@@ -549,6 +556,7 @@ struct tsens_context {
* @feat: features of the IP
* @fields: bitfield locations
* @ops: pointer to list of callbacks supported by this device
+ * @zeroc_en: variable to check zeroc interrupt sensor is enabled or not
* @debug_root: pointer to debugfs dentry for all tsens
* @debug: pointer to debugfs dentry for tsens controller
* @sensor: list of sensors attached to this device
@@ -568,6 +576,7 @@ struct tsens_priv {
struct tsens_features *feat;
const struct reg_field *fields;
const struct tsens_ops *ops;
+ bool zeroc_en;
struct dentry *debug_root;
struct dentry *debug;
@@ -580,6 +589,7 @@ void compute_intercept_slope(struct tsens_priv *priv, u32 *pt1, u32 *pt2, u32 mo
int init_common(struct tsens_priv *priv);
int get_temp_tsens_valid(const struct tsens_sensor *s, int *temp);
int get_temp_common(const struct tsens_sensor *s, int *temp);
+int get_tsens_zeroc_status(const struct tsens_sensor *s, int *temp);
/* TSENS target */
extern struct tsens_plat_data data_8960;
--
2.26.2
^ permalink raw reply related
* [PATCH v2 0/2] Add zeroc interrupt support to tsens driver
From: Manaf Meethalavalappu Pallikunhi @ 2020-05-17 10:46 UTC (permalink / raw)
To: Amit Kucheria, Andy Gross, Bjorn Andersson, Zhang Rui,
Daniel Lezcano, Rob Herring
Cc: linux-pm, linux-arm-msm, devicetree, linux-kernel,
Manaf Meethalavalappu Pallikunhi
The changes have dependency on merging tsens-common.c into tsens.c [1]
to merge first.
Dependencies:
[1] https://lkml.org/lkml/2020/4/29/1028
Changes in v2:
* Add zeroc interrupt support to tsens driver
* Update zeroc interrupt support in yaml
Manaf Meethalavalappu Pallikunhi (2):
drivers: thermal: tsens: Add zeroc interrupt support
dt-bindings: thermal: tsens: Add zeroc interrupt support in yaml
.../bindings/thermal/qcom-tsens.yaml | 21 ++++
drivers/thermal/qcom/tsens-v2.c | 5 +
drivers/thermal/qcom/tsens.c | 107 +++++++++++++++++-
drivers/thermal/qcom/tsens.h | 10 ++
4 files changed, 141 insertions(+), 2 deletions(-)
--
2.26.2
^ permalink raw reply
* [PATCH v9] arm64: dts: qcom: sc7180: Add WCN3990 WLAN module device node
From: Rakesh Pillai @ 2020-05-17 10:46 UTC (permalink / raw)
To: devicetree; +Cc: linux-arm-kernel, linux-kernel, linux-arm-msm, Rakesh Pillai
Add device node for the ath10k SNOC platform driver probe
and add resources required for WCN3990 on sc7180 soc.
Signed-off-by: Rakesh Pillai <pillair@codeaurora.org>
---
Changes from v8:
- Removed the qcom,msa-fixed-perm
---
arch/arm64/boot/dts/qcom/sc7180-idp.dts | 7 +++++++
arch/arm64/boot/dts/qcom/sc7180.dtsi | 27 +++++++++++++++++++++++++++
2 files changed, 34 insertions(+)
diff --git a/arch/arm64/boot/dts/qcom/sc7180-idp.dts b/arch/arm64/boot/dts/qcom/sc7180-idp.dts
index 4e9149d..38b102e 100644
--- a/arch/arm64/boot/dts/qcom/sc7180-idp.dts
+++ b/arch/arm64/boot/dts/qcom/sc7180-idp.dts
@@ -389,6 +389,13 @@
};
};
+&wifi {
+ status = "okay";
+ wifi-firmware {
+ iommus = <&apps_smmu 0xc2 0x1>;
+ };
+};
+
/* PINCTRL - additions to nodes defined in sc7180.dtsi */
&qspi_clk {
diff --git a/arch/arm64/boot/dts/qcom/sc7180.dtsi b/arch/arm64/boot/dts/qcom/sc7180.dtsi
index f1280e0..dd4e095 100644
--- a/arch/arm64/boot/dts/qcom/sc7180.dtsi
+++ b/arch/arm64/boot/dts/qcom/sc7180.dtsi
@@ -63,6 +63,11 @@
clock-frequency = <32764>;
#clock-cells = <0>;
};
+
+ wlan_fw_mem: memory@94100000 {
+ reg = <0 0x94100000 0 0x200000>;
+ no-map;
+ };
};
reserved_memory: reserved-memory {
@@ -944,6 +949,28 @@
};
};
+ wifi: wifi@18800000 {
+ compatible = "qcom,wcn3990-wifi";
+ reg = <0 0x18800000 0 0x800000>;
+ reg-names = "membase";
+ iommus = <&apps_smmu 0xc0 0x1>;
+ interrupts =
+ <GIC_SPI 414 IRQ_TYPE_LEVEL_HIGH /* CE0 */ >,
+ <GIC_SPI 415 IRQ_TYPE_LEVEL_HIGH /* CE1 */ >,
+ <GIC_SPI 416 IRQ_TYPE_LEVEL_HIGH /* CE2 */ >,
+ <GIC_SPI 417 IRQ_TYPE_LEVEL_HIGH /* CE3 */ >,
+ <GIC_SPI 418 IRQ_TYPE_LEVEL_HIGH /* CE4 */ >,
+ <GIC_SPI 419 IRQ_TYPE_LEVEL_HIGH /* CE5 */ >,
+ <GIC_SPI 420 IRQ_TYPE_LEVEL_HIGH /* CE6 */ >,
+ <GIC_SPI 421 IRQ_TYPE_LEVEL_HIGH /* CE7 */ >,
+ <GIC_SPI 422 IRQ_TYPE_LEVEL_HIGH /* CE8 */ >,
+ <GIC_SPI 423 IRQ_TYPE_LEVEL_HIGH /* CE9 */ >,
+ <GIC_SPI 424 IRQ_TYPE_LEVEL_HIGH /* CE10 */>,
+ <GIC_SPI 425 IRQ_TYPE_LEVEL_HIGH /* CE11 */>;
+ memory-region = <&wlan_fw_mem>;
+ status = "disabled";
+ };
+
config_noc: interconnect@1500000 {
compatible = "qcom,sc7180-config-noc";
reg = <0 0x01500000 0 0x28000>;
--
2.7.4
^ permalink raw reply related
* Re: [PATCH 2/2] dt-bindings: thermal: tsens: Add zeroc interrupt support in yaml
From: manafm @ 2020-05-17 10:32 UTC (permalink / raw)
To: Rob Herring
Cc: Andy Gross, Bjorn Andersson, Amit Kucheria, Zhang Rui,
Daniel Lezcano, linux-arm-msm, linux-pm, devicetree, linux-kernel
In-Reply-To: <20200505153819.GA19181@bogus>
On 2020-05-05 21:08, Rob Herring wrote:
> On Tue, 5 May 2020 16:42:04 +0530, Manaf Meethalavalappu Pallikunhi
> wrote:
>> Add 0C (zeroc) interrupt support for tsens in yaml.
>>
>> Signed-off-by: Manaf Meethalavalappu Pallikunhi
>> <manafm@codeaurora.org>
>> ---
>> Documentation/devicetree/bindings/thermal/qcom-tsens.yaml | 7 +++++--
>> 1 file changed, 5 insertions(+), 2 deletions(-)
>>
>
> My bot found errors running 'make dt_binding_check' on your patch:
>
> /builds/robherring/linux-dt-review/Documentation/devicetree/bindings/thermal/qcom-tsens.example.dt.yaml:
> thermal-sensor@c263000: interrupt-names: ['uplow', 'critical',
> 'zeroc'] is too long
> /builds/robherring/linux-dt-review/Documentation/devicetree/bindings/thermal/qcom-tsens.example.dt.yaml:
> thermal-sensor@c263000: interrupts: [[0, 506, 4], [0, 508, 4], [0,
> 510, 1]] is too long
>
> See https://patchwork.ozlabs.org/patch/1283470
>
> If you already ran 'make dt_binding_check' and didn't see the above
> error(s), then make sure dt-schema is up to date:
>
> pip3 install
> git+https://github.com/devicetree-org/dt-schema.git@master --upgrade
>
> Please check and re-submit.
Addressed above errors for
Documentation/devicetree/bindings/thermal/qcom-tsens.yaml in v2
^ permalink raw reply
* Re: [PATCH 0/2] Add 0C (zeorC) interrupt support to tsens driver
From: manafm @ 2020-05-17 10:30 UTC (permalink / raw)
To: Amit Kucheria
Cc: Andy Gross, Bjorn Andersson, Zhang Rui, Daniel Lezcano,
Rob Herring, linux-arm-msm, Linux PM list, DTML,
Linux Kernel Mailing List
In-Reply-To: <CAP245DWxDVBAXU_1uqfA0iuajVCZvZOL4HG4VOOwZ1qMSuuMSA@mail.gmail.com>
On 2020-05-05 17:42, Amit Kucheria wrote:
> Hi Manaf,
>
> Thanks for sending this.
>
> Typo: zeorc in subject line.
Done
>
>
> On Tue, May 5, 2020 at 4:42 PM Manaf Meethalavalappu Pallikunhi
> <manafm@codeaurora.org> wrote:
>>
>> Changes:
>> * Add zeroc interrupt support to tsens driver
>> * Update zeroc interrupt support in yaml
>>
>> Manaf Meethalavalappu Pallikunhi (2):
>> drivers: thermal: tsens: Add 0C (zeorC) interrupt support
>> dt-bindings: thermal: tsens: Add zeroc interrupt support in yaml
>>
>> .../bindings/thermal/qcom-tsens.yaml | 7 +-
>> drivers/thermal/qcom/tsens-common.c | 72
>> ++++++++++++++++++-
>> drivers/thermal/qcom/tsens-v2.c | 7 ++
>> drivers/thermal/qcom/tsens.c | 51 +++++++++++--
>> drivers/thermal/qcom/tsens.h | 11 +++
>> 5 files changed, 140 insertions(+), 8 deletions(-)
>>
>> --
>> 2.26.2
^ permalink raw reply
* Re: [PATCH 2/2] dt-bindings: thermal: tsens: Add zeroc interrupt support in yaml
From: manafm @ 2020-05-17 10:30 UTC (permalink / raw)
To: Amit Kucheria
Cc: Andy Gross, Bjorn Andersson, Zhang Rui, Daniel Lezcano,
Rob Herring, linux-arm-msm, Linux PM list, DTML,
Linux Kernel Mailing List
In-Reply-To: <CAP245DX2=bY4kCERgnm_L26o9mY7CoOZRScXmXwfe-bAYczOnQ@mail.gmail.com>
On 2020-05-05 17:41, Amit Kucheria wrote:
> On Tue, May 5, 2020 at 4:43 PM Manaf Meethalavalappu Pallikunhi
> <manafm@codeaurora.org> wrote:
>>
>> Add 0C (zeroc) interrupt support for tsens in yaml.
>>
>> Signed-off-by: Manaf Meethalavalappu Pallikunhi
>> <manafm@codeaurora.org>
>> ---
>> Documentation/devicetree/bindings/thermal/qcom-tsens.yaml | 7 +++++--
>> 1 file changed, 5 insertions(+), 2 deletions(-)
>>
>> diff --git a/Documentation/devicetree/bindings/thermal/qcom-tsens.yaml
>> b/Documentation/devicetree/bindings/thermal/qcom-tsens.yaml
>> index 2ddd39d96766..8a0893f77d20 100644
>> --- a/Documentation/devicetree/bindings/thermal/qcom-tsens.yaml
>> +++ b/Documentation/devicetree/bindings/thermal/qcom-tsens.yaml
>> @@ -52,12 +52,14 @@ properties:
>> items:
>> - description: Combined interrupt if upper or lower threshold
>> crossed
>> - description: Interrupt if critical threshold crossed
>> + - description: Interrupt if zeroC threshold is crossed
>>
>> interrupt-names:
>> minItems: 1
>> items:
>> - const: uplow
>> - const: critical
>> + - const: zeroc
>>
>> nvmem-cells:
>> minItems: 1
>> @@ -168,8 +170,9 @@ examples:
>> <0xc222000 0x1ff>;
>>
>> interrupts = <GIC_SPI 506 IRQ_TYPE_LEVEL_HIGH>,
>> - <GIC_SPI 508 IRQ_TYPE_LEVEL_HIGH>;
>> - interrupt-names = "uplow", "critical";
>> + <GIC_SPI 508 IRQ_TYPE_LEVEL_HIGH>,
>> + <GIC_SPI 510 IRQ_TYPE_EDGE_RISING>;
>> + interrupt-names = "uplow", "critical", "zeroc";
>
>
> Add a new example for v2 with 0C interrupt here instead of reusing the
> old one.
Done
>
>> #qcom,sensors = <13>;
>> #thermal-sensor-cells = <1>;
>> --
>> 2.26.2
^ permalink raw reply
* Re: [PATCH 1/2] drivers: thermal: tsens: Add 0C (zeorC) interrupt support
From: manafm @ 2020-05-17 10:28 UTC (permalink / raw)
To: Amit Kucheria
Cc: Andy Gross, Bjorn Andersson, Zhang Rui, Daniel Lezcano,
Rob Herring, linux-arm-msm, Linux PM list, DTML,
Linux Kernel Mailing List
In-Reply-To: <CAP245DV3-WXa1183tY=AzREAfzt3nXYugngG+zn6pEjXLc68cA@mail.gmail.com>
On 2020-05-05 17:39, Amit Kucheria wrote:
> Hi Manaf,
>
> Typo: fix zeorC in subject line.
Done
>
> Please rebase this patch[1] on top of my patch merging tsens-common.c
> and tsens.c.
>
> [1]
> https://lore.kernel.org/linux-arm-msm/e30e2ba6fa5c007983afd4d7d4e0311c0b57917a.1588183879.git.amit.kucheria@linaro.org/
Done
>
> On Tue, May 5, 2020 at 4:42 PM Manaf Meethalavalappu Pallikunhi
> <manafm@codeaurora.org> wrote:
>>
>> TSENS IP v2.6+ adds 0C interrupt support. It triggers set
>> interrupt when aggregated minimum temperature of all TSENS falls
>> below 0C preset threshold and triggers reset interrupt when aggregate
>> minimum temperature of all TSENS crosses above reset threshold.
>> Add support for this interrupt in the driver.
>>
>> It adds another sensor to the of-thermal along with all individual
>> TSENS. It enables to add any mitigation for 0C interrupt.
>>
>> Signed-off-by: Manaf Meethalavalappu Pallikunhi
>> <manafm@codeaurora.org>
>> ---
>> drivers/thermal/qcom/tsens-common.c | 72
>> ++++++++++++++++++++++++++++-
>> drivers/thermal/qcom/tsens-v2.c | 7 +++
>> drivers/thermal/qcom/tsens.c | 51 ++++++++++++++++++--
>> drivers/thermal/qcom/tsens.h | 11 +++++
>> 4 files changed, 135 insertions(+), 6 deletions(-)
>>
>> diff --git a/drivers/thermal/qcom/tsens-common.c
>> b/drivers/thermal/qcom/tsens-common.c
>> index 172545366636..44e7edeb9a90 100644
>> --- a/drivers/thermal/qcom/tsens-common.c
>> +++ b/drivers/thermal/qcom/tsens-common.c
>> @@ -198,7 +198,8 @@ static void tsens_set_interrupt_v1(struct
>> tsens_priv *priv, u32 hw_id,
>> index = LOW_INT_CLEAR_0 + hw_id;
>> break;
>> case CRITICAL:
>> - /* No critical interrupts before v2 */
>> + case ZEROC:
>> + /* No critical and 0c interrupts before v2 */
>> return;
>> }
>> regmap_field_write(priv->rf[index], enable ? 0 : 1);
>> @@ -229,6 +230,9 @@ static void tsens_set_interrupt_v2(struct
>> tsens_priv *priv, u32 hw_id,
>> index_mask = CRIT_INT_MASK_0 + hw_id;
>> index_clear = CRIT_INT_CLEAR_0 + hw_id;
>> break;
>> + case ZEROC:
>> + /* Nothing to handle for 0c interrupt */
>> + return;
>> }
>>
>> if (enable) {
>> @@ -360,6 +364,34 @@ static inline u32 masked_irq(u32 hw_id, u32 mask,
>> enum tsens_ver ver)
>> return 0;
>> }
>>
>> +/**
>> + * tsens_0c_irq_thread - Threaded interrupt handler for 0c interrupt
>
> Let's use zeroc instead of 0c in the function and variable names and
> comments everywhere. Easier to grep and better consistency too.
Done
>
>> + * @irq: irq number
>> + * @data: tsens controller private data
>> + *
>> + * Whenever interrupt triggers notify thermal framework using
>> + * thermal_zone_device_update() to update cold temperature
>> mitigation.
>
> How is this mitigation updated?
Updated comment section
>> + *
>> + * Return: IRQ_HANDLED
>> + */
>> +irqreturn_t tsens_0c_irq_thread(int irq, void *data)
>> +{
>> + struct tsens_priv *priv = data;
>> + struct tsens_sensor *s = &priv->sensor[priv->num_sensors];
>> + int temp, ret;
>> +
>> + ret = regmap_field_read(priv->rf[TSENS_0C_STATUS], &temp);
>> + if (ret)
>> + return ret;
>> +
>> + dev_dbg(priv->dev, "[%u] %s: 0c interrupt is %s\n",
>> + s->hw_id, __func__, temp ? "triggered" : "cleared");
>
> So triggered is printed for non-zero (including negative) values?
This zeroc hardware generates each interrupt when any of the TSENS that
it monitors goes below 5C or above 10c. These thresholds are not
configurable. Hence we don't expect this to be changed from kernel side.
So this sensor (status register) will read 0 or 1. 1 means soc
temperature is in cold condition and 0 means it is in normal
temperature.
>
>> +
>> + thermal_zone_device_update(s->tzd, THERMAL_EVENT_UNSPECIFIED);
>> +
>> + return IRQ_HANDLED;
>> +}
>> +
>> /**
>> * tsens_critical_irq_thread() - Threaded handler for critical
>> interrupts
>> * @irq: irq number
>> @@ -566,6 +598,20 @@ void tsens_disable_irq(struct tsens_priv *priv)
>> regmap_field_write(priv->rf[INT_EN], 0);
>> }
>>
>> +int tsens_get_0c_int_status(const struct tsens_sensor *s, int *temp)
>> +{
>> + struct tsens_priv *priv = s->priv;
>> + int last_temp = 0, ret;
>> +
>> + ret = regmap_field_read(priv->rf[TSENS_0C_STATUS],
>> &last_temp);
>> + if (ret)
>> + return ret;
>> +
>> + *temp = last_temp;
>> +
>> + return 0;
>> +}
>> +
>> int get_temp_tsens_valid(const struct tsens_sensor *s, int *temp)
>> {
>> struct tsens_priv *priv = s->priv;
>> @@ -833,6 +879,30 @@ int __init init_common(struct tsens_priv *priv)
>> regmap_field_write(priv->rf[CC_MON_MASK], 1);
>> }
>>
>> + if (tsens_version(priv) > VER_1_X && ver_minor > 5) {
>> + /* 0C interrupt is present only on v2.6+ */
>> + priv->rf[TSENS_0C_INT_EN] =
>> devm_regmap_field_alloc(dev,
>> + priv->srot_map,
>> +
>> priv->fields[TSENS_0C_INT_EN]);
>> + if (IS_ERR(priv->rf[TSENS_0C_INT_EN])) {
>> + ret = PTR_ERR(priv->rf[TSENS_0C_INT_EN]);
>> + goto err_put_device;
>> + }
>> +
>> + /* Check whether 0C interrupt is enabled or not */
>> + regmap_field_read(priv->rf[TSENS_0C_INT_EN],
>> &enabled);
>> + if (enabled) {
>> + priv->feat->zero_c_int = 1;
>
> This should be done at the beginning of the block where you check our
> version is > 2.6 since the flag only says whether the feature is
> present.
Done
>
>> + priv->rf[TSENS_0C_STATUS] =
>> devm_regmap_field_alloc(dev,
>> + priv->tm_map,
>> +
>> priv->fields[TSENS_0C_STATUS]);
>> + if (IS_ERR(priv->rf[TSENS_0C_STATUS])) {
>> + ret =
>> PTR_ERR(priv->rf[TSENS_0C_STATUS]);
>> + goto err_put_device;
>> + }
>> + }
>> + }
>> +
>> spin_lock_init(&priv->ul_lock);
>> tsens_enable_irq(priv);
>> tsens_debug_init(op);
>> diff --git a/drivers/thermal/qcom/tsens-v2.c
>> b/drivers/thermal/qcom/tsens-v2.c
>> index b293ed32174b..ce80d82c7255 100644
>> --- a/drivers/thermal/qcom/tsens-v2.c
>> +++ b/drivers/thermal/qcom/tsens-v2.c
>> @@ -11,6 +11,7 @@
>> /* ----- SROT ------ */
>> #define SROT_HW_VER_OFF 0x0000
>> #define SROT_CTRL_OFF 0x0004
>> +#define SROT_OC_CTRL_OFF 0x0018
>>
>> /* ----- TM ------ */
>> #define TM_INT_EN_OFF 0x0004
>> @@ -23,6 +24,7 @@
>> #define TM_Sn_UPPER_LOWER_THRESHOLD_OFF 0x0020
>> #define TM_Sn_CRITICAL_THRESHOLD_OFF 0x0060
>> #define TM_Sn_STATUS_OFF 0x00a0
>> +#define TM_0C_INT_STATUS_OFF 0x00e0
>> #define TM_TRDY_OFF 0x00e4
>> #define TM_WDOG_LOG_OFF 0x013c
>>
>> @@ -45,6 +47,7 @@ static const struct reg_field
>> tsens_v2_regfields[MAX_REGFIELDS] = {
>> /* CTRL_OFF */
>> [TSENS_EN] = REG_FIELD(SROT_CTRL_OFF, 0, 0),
>> [TSENS_SW_RST] = REG_FIELD(SROT_CTRL_OFF, 1, 1),
>> + [TSENS_0C_INT_EN] = REG_FIELD(SROT_OC_CTRL_OFF, 0, 0),
>>
>> /* ----- TM ------ */
>> /* INTERRUPT ENABLE */
>> @@ -86,6 +89,9 @@ static const struct reg_field
>> tsens_v2_regfields[MAX_REGFIELDS] = {
>> REG_FIELD_FOR_EACH_SENSOR16(CRITICAL_STATUS, TM_Sn_STATUS_OFF,
>> 19, 19),
>> REG_FIELD_FOR_EACH_SENSOR16(MAX_STATUS, TM_Sn_STATUS_OFF,
>> 20, 20),
>>
>> + /* 0C INETRRUPT STATUS */
>
> Typo: Interrupt
>
>> + [TSENS_0C_STATUS] = REG_FIELD(TM_0C_INT_STATUS_OFF, 0, 0),
>> +
>> /* TRDY: 1=ready, 0=in progress */
>> [TRDY] = REG_FIELD(TM_TRDY_OFF, 0, 0),
>> };
>> @@ -93,6 +99,7 @@ static const struct reg_field
>> tsens_v2_regfields[MAX_REGFIELDS] = {
>> static const struct tsens_ops ops_generic_v2 = {
>> .init = init_common,
>> .get_temp = get_temp_tsens_valid,
>> + .get_0c_status = tsens_get_0c_int_status,
>> };
>>
>> struct tsens_plat_data data_tsens_v2 = {
>> diff --git a/drivers/thermal/qcom/tsens.c
>> b/drivers/thermal/qcom/tsens.c
>> index 2f77d235cf73..e60870c53383 100644
>> --- a/drivers/thermal/qcom/tsens.c
>> +++ b/drivers/thermal/qcom/tsens.c
>> @@ -14,6 +14,17 @@
>> #include <linux/thermal.h>
>> #include "tsens.h"
>>
>> +static int tsens_0c_get_temp(void *data, int *temp)
>> +{
>> + struct tsens_sensor *s = data;
>> + struct tsens_priv *priv = s->priv;
>> +
>> + if (priv->ops->get_0c_status)
>> + return priv->ops->get_0c_status(s, temp);
>> +
>> + return -ENOTSUPP;
>> +}
>> +
>> static int tsens_get_temp(void *data, int *temp)
>> {
>> struct tsens_sensor *s = data;
>> @@ -85,6 +96,10 @@ static const struct thermal_zone_of_device_ops
>> tsens_of_ops = {
>> .set_trips = tsens_set_trips,
>> };
>>
>> +static const struct thermal_zone_of_device_ops tsens_0c_of_ops = {
>> + .get_temp = tsens_0c_get_temp,
>> +};
>> +
>> static int tsens_register_irq(struct tsens_priv *priv, char *irqname,
>> irq_handler_t thread_fn)
>> {
>> @@ -142,6 +157,21 @@ static int tsens_register(struct tsens_priv
>> *priv)
>> ret = tsens_register_irq(priv, "critical",
>> tsens_critical_irq_thread);
>>
>> + if (priv->feat->zero_c_int) {
>> + priv->sensor[priv->num_sensors].priv = priv;
>> + tzd = devm_thermal_zone_of_sensor_register(priv->dev,
>> +
>> priv->sensor[priv->num_sensors].hw_id,
>> +
>> &priv->sensor[priv->num_sensors],
>> + &tsens_0c_of_ops);
>> + if (IS_ERR(tzd)) {
>> + ret = 0;
>> + return ret;
>> + }
>> +
>> + priv->sensor[priv->num_sensors].tzd = tzd;
>
> Why can't this happen in the previous loop, but increase the loop to
> <= num_sensors? It is duplicated code.
I think if i change default loop logic to <= num_sensors, it will break
other legacy targets, right ?
My idea is to guard all changes related to zeroc under zeroc related
feature flag.
Again, since we cannot configure any threshold from kernel side, there
is no set_trip ops for this sensor, so we need to call register function
differently in compared to regular sensor
>
>> + ret = tsens_register_irq(priv, "zeroc",
>> tsens_0c_irq_thread);
>> + }
>> +
>> return ret;
>> }
>>
>> @@ -178,11 +208,22 @@ static int tsens_probe(struct platform_device
>> *pdev)
>> return -EINVAL;
>> }
>>
>> - priv = devm_kzalloc(dev,
>> - struct_size(priv, sensor, num_sensors),
>> - GFP_KERNEL);
>> - if (!priv)
>> - return -ENOMEM;
>> + /* Check for 0c interrupt is enabled or not */
>> + if (platform_get_irq_byname(pdev, "zeroc") > 0) {
>> + priv = devm_kzalloc(dev,
>> + struct_size(priv, sensor, num_sensors
>> + 1),
>> + GFP_KERNEL);
>
> Instead of doing this, simply do the following,
>
> if (platform_get_irq_byname(pdev, "zeroc") > 0) {
> num_sensors++;
>
> The kzalloc will just work then, no?
I just changed logic in v2. Basically this zeroc feature is an
optional. There is a chance that we don't need to enable in software
even though hardware support is present.
So I used another variable to check whether feature is enabled or not by
checking DT interrupt configuration.
>
>> + if (!priv)
>> + return -ENOMEM;
>> + /* Use Max sensor index as 0c sensor hw_id */
>> + priv->sensor[num_sensors].hw_id =
>> data->feat->max_sensors;
>> + } else {
>> + priv = devm_kzalloc(dev,
>> + struct_size(priv, sensor,
>> num_sensors),
>> + GFP_KERNEL);
>> + if (!priv)
>> + return -ENOMEM;
>> + }
>>
>> priv->dev = dev;
>> priv->num_sensors = num_sensors;
>> diff --git a/drivers/thermal/qcom/tsens.h
>> b/drivers/thermal/qcom/tsens.h
>> index 502acf0e6828..5b53a0352b4d 100644
>> --- a/drivers/thermal/qcom/tsens.h
>> +++ b/drivers/thermal/qcom/tsens.h
>> @@ -34,6 +34,7 @@ enum tsens_irq_type {
>> LOWER,
>> UPPER,
>> CRITICAL,
>> + ZEROC,
>> };
>>
>> /**
>> @@ -64,6 +65,7 @@ struct tsens_sensor {
>> * @suspend: Function to suspend the tsens device
>> * @resume: Function to resume the tsens device
>> * @get_trend: Function to get the thermal/temp trend
>> + * @get_0c_status: Function to get the 0c interrupt status
>> */
>> struct tsens_ops {
>> /* mandatory callbacks */
>> @@ -76,6 +78,7 @@ struct tsens_ops {
>> int (*suspend)(struct tsens_priv *priv);
>> int (*resume)(struct tsens_priv *priv);
>> int (*get_trend)(struct tsens_sensor *s, enum thermal_trend
>> *trend);
>> + int (*get_0c_status)(const struct tsens_sensor *s, int *temp);
>> };
>>
>> #define REG_FIELD_FOR_EACH_SENSOR11(_name, _offset, _startbit,
>> _stopbit) \
>> @@ -161,6 +164,8 @@ enum regfield_ids {
>> TSENS_SW_RST,
>> SENSOR_EN,
>> CODE_OR_TEMP,
>> + /* 0C CTRL OFFSET */
>> + TSENS_0C_INT_EN,
>>
>> /* ----- TM ------ */
>> /* TRDY */
>> @@ -485,6 +490,8 @@ enum regfield_ids {
>> MAX_STATUS_14,
>> MAX_STATUS_15,
>>
>> + TSENS_0C_STATUS, /* 0C INTERRUPT status */
>> +
>> /* Keep last */
>> MAX_REGFIELDS
>> };
>> @@ -497,6 +504,7 @@ enum regfield_ids {
>> * @srot_split: does the IP neatly splits the register space into
>> SROT and TM,
>> * with SROT only being available to secure boot
>> firmware?
>> * @has_watchdog: does this IP support watchdog functionality?
>> + * @zero_c_int: does this IP support 0C interrupt ?
>> * @max_sensors: maximum sensors supported by this version of the IP
>> */
>> struct tsens_features {
>> @@ -505,6 +513,7 @@ struct tsens_features {
>> unsigned int adc:1;
>> unsigned int srot_split:1;
>> unsigned int has_watchdog:1;
>> + unsigned int zero_c_int:1;
>
> zeroc_interrupt
Done
>
>> unsigned int max_sensors;
>> };
>>
>> @@ -580,11 +589,13 @@ void compute_intercept_slope(struct tsens_priv
>> *priv, u32 *pt1, u32 *pt2, u32 mo
>> int init_common(struct tsens_priv *priv);
>> int get_temp_tsens_valid(const struct tsens_sensor *s, int *temp);
>> int get_temp_common(const struct tsens_sensor *s, int *temp);
>> +int tsens_get_0c_int_status(const struct tsens_sensor *s, int *temp);
>> int tsens_enable_irq(struct tsens_priv *priv);
>> void tsens_disable_irq(struct tsens_priv *priv);
>> int tsens_set_trips(void *_sensor, int low, int high);
>> irqreturn_t tsens_irq_thread(int irq, void *data);
>> irqreturn_t tsens_critical_irq_thread(int irq, void *data);
>> +irqreturn_t tsens_0c_irq_thread(int irq, void *data);
>>
>> /* TSENS target */
>> extern struct tsens_plat_data data_8960;
>> --
>> 2.26.2
^ permalink raw reply
* Re: [PATCH v1 3/3] clk: qcom: gcc: Add support for Secure control source clock
From: Taniya Das @ 2020-05-17 10:02 UTC (permalink / raw)
To: Stephen Boyd, Michael Turquette
Cc: David Brown, Rajendra Nayak, linux-arm-msm, linux-soc, linux-clk,
linux-kernel, Andy Gross, devicetree, robh, robh+dt
In-Reply-To: <158438098823.88485.2094714876575396381@swboyd.mtv.corp.google.com>
Hello Stephen,
Thanks for your review.
On 3/16/2020 11:19 PM, Stephen Boyd wrote:
> Quoting Taniya Das (2020-03-16 03:54:42)
>> diff --git a/drivers/clk/qcom/gcc-sc7180.c b/drivers/clk/qcom/gcc-sc7180.c
>> index ad75847..3302f19 100644
>> --- a/drivers/clk/qcom/gcc-sc7180.c
>> +++ b/drivers/clk/qcom/gcc-sc7180.c
>> @@ -817,6 +817,26 @@ static struct clk_rcg2 gcc_usb3_prim_phy_aux_clk_src = {
>> },
>> };
>>
>> +static const struct freq_tbl ftbl_gcc_sec_ctrl_clk_src[] = {
>> + F(4800000, P_BI_TCXO, 4, 0, 0),
>> + F(19200000, P_BI_TCXO, 1, 0, 0),
>> + { }
>> +};
>> +
>> +static struct clk_rcg2 gcc_sec_ctrl_clk_src = {
>> + .cmd_rcgr = 0x3d030,
>> + .mnd_width = 0,
>> + .hid_width = 5,
>> + .parent_map = gcc_parent_map_3,
>> + .freq_tbl = ftbl_gcc_sec_ctrl_clk_src,
>> + .clkr.hw.init = &(struct clk_init_data){
>> + .name = "gcc_sec_ctrl_clk_src",
>> + .parent_data = gcc_parent_data_3,
>> + .num_parents = 3,
>
> ARRAY_SIZE please.
>
Will take care of the same.
>> + .ops = &clk_rcg2_ops,
>> + },
>> +};
>> +
>> static struct clk_branch gcc_aggre_ufs_phy_axi_clk = {
>> .halt_reg = 0x82024,
>> .halt_check = BRANCH_HALT_DELAY,
--
QUALCOMM INDIA, on behalf of Qualcomm Innovation Center, Inc. is a member
of Code Aurora Forum, hosted by The Linux Foundation.
--
^ permalink raw reply
* Re: [PATCH v1 1/3] clk: qcom: gcc: Add support for a new frequency for SC7180
From: Taniya Das @ 2020-05-17 10:01 UTC (permalink / raw)
To: Stephen Boyd, Michael Turquette
Cc: David Brown, Rajendra Nayak, linux-arm-msm, linux-soc, linux-clk,
linux-kernel, Andy Gross, devicetree, robh, robh+dt
In-Reply-To: <158438095454.88485.11063617239206162025@swboyd.mtv.corp.google.com>
Hello Stephen,
Thanks for your review.
On 3/16/2020 11:19 PM, Stephen Boyd wrote:
> Quoting Taniya Das (2020-03-16 03:54:40)
>> There is a requirement to support 51.2MHz from GPLL6 for qup clocks,
>> thus update the frequency table and parent data/map to use the GPLL6
>> source PLL.
>>
>> Signed-off-by: Taniya Das <tdas@codeaurora.org>
>> ---
>
> Any Fixes: tag for this? I guess the beginning of this driver being
> introduced?
>
Sure, will add the same.
>> drivers/clk/qcom/gcc-sc7180.c | 73 ++++++++++++++++++++++---------------------
>> 1 file changed, 37 insertions(+), 36 deletions(-)
>>
>> diff --git a/drivers/clk/qcom/gcc-sc7180.c b/drivers/clk/qcom/gcc-sc7180.c
>> index 7f59fb8..ad75847 100644
>> --- a/drivers/clk/qcom/gcc-sc7180.c
>> +++ b/drivers/clk/qcom/gcc-sc7180.c
>> @@ -405,8 +406,8 @@ static const struct freq_tbl ftbl_gcc_qupv3_wrap0_s0_clk_src[] = {
>>
>> static struct clk_init_data gcc_qupv3_wrap0_s0_clk_src_init = {
>> .name = "gcc_qupv3_wrap0_s0_clk_src",
>> - .parent_data = gcc_parent_data_0,
>> - .num_parents = 4,
>> + .parent_data = gcc_parent_data_1,
>
> This should have been done initially. We shouldn't need to describe
> "new" parents when they have always been there. Are there other clks in
> this driver that actually have more parents than we've currently
> described? If so, please fix them.
>
The auto generation script does not consider to define the parent unless
it is used in the frequency table to derive a frequency. For now I
didn't find any other sources missed.
>> + .num_parents = 5,
>
> Can you use ARRAY_SIZE(gcc_parent_data_1) instead? That way this isn't a
> hard-coded value.
>
Yes will take care of it too.
>> .ops = &clk_rcg2_ops,
>> };
>>
--
QUALCOMM INDIA, on behalf of Qualcomm Innovation Center, Inc. is a member
of Code Aurora Forum, hosted by The Linux Foundation.
--
^ permalink raw reply
* [PATCH v2 4/4] clk: qcom: lpass: Add support for LPASS clock controller for SC7180
From: Taniya Das @ 2020-05-17 9:22 UTC (permalink / raw)
To: Stephen Boyd, Michael Turquette
Cc: David Brown, Rajendra Nayak, linux-arm-msm, linux-soc, linux-clk,
linux-kernel, Andy Gross, devicetree, robh, robh+dt, Taniya Das
In-Reply-To: <1589707344-8871-1-git-send-email-tdas@codeaurora.org>
The Low Power Audio subsystem clocks are required for Audio client
to be able to request for the clocks and power domains.
Signed-off-by: Taniya Das <tdas@codeaurora.org>
---
drivers/clk/qcom/Kconfig | 9 +
drivers/clk/qcom/Makefile | 1 +
drivers/clk/qcom/lpasscorecc-sc7180.c | 479 ++++++++++++++++++++++++++++++++++
3 files changed, 489 insertions(+)
create mode 100644 drivers/clk/qcom/lpasscorecc-sc7180.c
diff --git a/drivers/clk/qcom/Kconfig b/drivers/clk/qcom/Kconfig
index 11ec6f4..732e91d 100644
--- a/drivers/clk/qcom/Kconfig
+++ b/drivers/clk/qcom/Kconfig
@@ -272,6 +272,15 @@ config SC_GCC_7180
Say Y if you want to use peripheral devices such as UART, SPI,
I2C, USB, UFS, SDCC, etc.
+config SC_LPASS_CORECC_7180
+ tristate "SC7180 LPASS Core Clock Controller"
+ select SC_GCC_7180
+ help
+ Support for the LPASS(Low Power Audio Subsystem) core clock controller
+ on SC7180 devices.
+ Say Y if you want to use LPASS clocks and power domains of the LPASS
+ core clock controller.
+
config SC_GPUCC_7180
tristate "SC7180 Graphics Clock Controller"
select SC_GCC_7180
diff --git a/drivers/clk/qcom/Makefile b/drivers/clk/qcom/Makefile
index 691efbf..cb29262 100644
--- a/drivers/clk/qcom/Makefile
+++ b/drivers/clk/qcom/Makefile
@@ -50,6 +50,7 @@ obj-$(CONFIG_QCS_TURING_404) += turingcc-qcs404.o
obj-$(CONFIG_SC_DISPCC_7180) += dispcc-sc7180.o
obj-$(CONFIG_SC_GCC_7180) += gcc-sc7180.o
obj-$(CONFIG_SC_GPUCC_7180) += gpucc-sc7180.o
+obj-$(CONFIG_SC_LPASS_CORECC_7180) += lpasscorecc-sc7180.o
obj-$(CONFIG_SC_MSS_7180) += mss-sc7180.o
obj-$(CONFIG_SC_VIDEOCC_7180) += videocc-sc7180.o
obj-$(CONFIG_SDM_CAMCC_845) += camcc-sdm845.o
diff --git a/drivers/clk/qcom/lpasscorecc-sc7180.c b/drivers/clk/qcom/lpasscorecc-sc7180.c
new file mode 100644
index 0000000..86e3599
--- /dev/null
+++ b/drivers/clk/qcom/lpasscorecc-sc7180.c
@@ -0,0 +1,479 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Copyright (c) 2020, The Linux Foundation. All rights reserved.
+ */
+
+#include <linux/clk-provider.h>
+#include <linux/err.h>
+#include <linux/module.h>
+#include <linux/of_device.h>
+#include <linux/pm_clock.h>
+#include <linux/pm_runtime.h>
+#include <linux/of.h>
+#include <linux/regmap.h>
+
+#include <dt-bindings/clock/qcom,lpasscorecc-sc7180.h>
+
+#include "clk-alpha-pll.h"
+#include "clk-branch.h"
+#include "clk-rcg.h"
+#include "clk-regmap.h"
+#include "common.h"
+#include "gdsc.h"
+
+enum {
+ P_BI_TCXO,
+ P_LPASS_LPAAUDIO_DIG_PLL_OUT_ODD,
+ P_SLEEP_CLK,
+};
+
+static struct pll_vco fabia_vco[] = {
+ { 249600000, 2000000000, 0 },
+};
+
+static const struct alpha_pll_config lpass_lpaaudio_dig_pll_config = {
+ .l = 0x20,
+ .alpha = 0x0,
+ .config_ctl_val = 0x20485699,
+ .config_ctl_hi_val = 0x00002067,
+ .test_ctl_val = 0x40000000,
+ .test_ctl_hi_val = 0x00000000,
+ .user_ctl_val = 0x00005105,
+ .user_ctl_hi_val = 0x00004805,
+};
+
+static const u8 clk_alpha_pll_regs_offset[][PLL_OFF_MAX_REGS] = {
+ [CLK_ALPHA_PLL_TYPE_FABIA] = {
+ [PLL_OFF_L_VAL] = 0x04,
+ [PLL_OFF_CAL_L_VAL] = 0x8,
+ [PLL_OFF_USER_CTL] = 0x0c,
+ [PLL_OFF_USER_CTL_U] = 0x10,
+ [PLL_OFF_USER_CTL_U1] = 0x14,
+ [PLL_OFF_CONFIG_CTL] = 0x18,
+ [PLL_OFF_CONFIG_CTL_U] = 0x1C,
+ [PLL_OFF_CONFIG_CTL_U1] = 0x20,
+ [PLL_OFF_TEST_CTL] = 0x24,
+ [PLL_OFF_TEST_CTL_U] = 0x28,
+ [PLL_OFF_STATUS] = 0x30,
+ [PLL_OFF_OPMODE] = 0x38,
+ [PLL_OFF_FRAC] = 0x40,
+ },
+};
+
+static struct clk_alpha_pll lpass_lpaaudio_dig_pll = {
+ .offset = 0x1000,
+ .vco_table = fabia_vco,
+ .num_vco = ARRAY_SIZE(fabia_vco),
+ .regs = clk_alpha_pll_regs_offset[CLK_ALPHA_PLL_TYPE_FABIA],
+ .clkr = {
+ .hw.init = &(struct clk_init_data){
+ .name = "lpass_lpaaudio_dig_pll",
+ .parent_data = &(const struct clk_parent_data){
+ .fw_name = "bi_tcxo",
+ .name = "bi_tcxo",
+ },
+ .num_parents = 1,
+ .ops = &clk_alpha_pll_fabia_ops,
+ },
+ },
+};
+
+static const struct clk_div_table
+ post_div_table_lpass_lpaaudio_dig_pll_out_odd[] = {
+ { 0x5, 5 },
+ { }
+};
+
+static struct clk_alpha_pll_postdiv lpass_lpaaudio_dig_pll_out_odd = {
+ .offset = 0x1000,
+ .post_div_shift = 12,
+ .post_div_table = post_div_table_lpass_lpaaudio_dig_pll_out_odd,
+ .num_post_div =
+ ARRAY_SIZE(post_div_table_lpass_lpaaudio_dig_pll_out_odd),
+ .width = 4,
+ .regs = clk_alpha_pll_regs[CLK_ALPHA_PLL_TYPE_FABIA],
+ .clkr.hw.init = &(struct clk_init_data){
+ .name = "lpass_lpaaudio_dig_pll_out_odd",
+ .parent_data = &(const struct clk_parent_data){
+ .hw = &lpass_lpaaudio_dig_pll.clkr.hw,
+ },
+ .num_parents = 1,
+ .flags = CLK_SET_RATE_PARENT,
+ .ops = &clk_alpha_pll_postdiv_fabia_ops,
+ },
+};
+
+static const struct parent_map lpass_core_cc_parent_map_0[] = {
+ { P_BI_TCXO, 0 },
+ { P_LPASS_LPAAUDIO_DIG_PLL_OUT_ODD, 5 },
+};
+
+static const struct clk_parent_data lpass_core_cc_parent_data_0[] = {
+ { .fw_name = "bi_tcxo", .name = "bi_tcxo" },
+ { .hw = &lpass_lpaaudio_dig_pll_out_odd.clkr.hw },
+};
+
+static const struct parent_map lpass_core_cc_parent_map_2[] = {
+ { P_BI_TCXO, 0 },
+};
+
+static struct clk_rcg2 core_clk_src = {
+ .cmd_rcgr = 0x1d000,
+ .mnd_width = 8,
+ .hid_width = 5,
+ .parent_map = lpass_core_cc_parent_map_2,
+ .clkr.hw.init = &(struct clk_init_data){
+ .name = "core_clk_src",
+ .parent_data = &(const struct clk_parent_data){
+ .fw_name = "bi_tcxo",
+ },
+ .num_parents = 1,
+ .ops = &clk_rcg2_ops,
+ },
+};
+
+static const struct freq_tbl ftbl_ext_mclk0_clk_src[] = {
+ F(9600000, P_BI_TCXO, 2, 0, 0),
+ F(19200000, P_BI_TCXO, 1, 0, 0),
+ { }
+};
+
+static const struct freq_tbl ftbl_ext_lpaif_clk_src[] = {
+ F(256000, P_LPASS_LPAAUDIO_DIG_PLL_OUT_ODD, 15, 1, 32),
+ F(512000, P_LPASS_LPAAUDIO_DIG_PLL_OUT_ODD, 15, 1, 16),
+ F(768000, P_LPASS_LPAAUDIO_DIG_PLL_OUT_ODD, 10, 1, 16),
+ F(1024000, P_LPASS_LPAAUDIO_DIG_PLL_OUT_ODD, 15, 1, 8),
+ F(1536000, P_LPASS_LPAAUDIO_DIG_PLL_OUT_ODD, 10, 1, 8),
+ F(2048000, P_LPASS_LPAAUDIO_DIG_PLL_OUT_ODD, 15, 1, 4),
+ F(3072000, P_LPASS_LPAAUDIO_DIG_PLL_OUT_ODD, 10, 1, 4),
+ F(4096000, P_LPASS_LPAAUDIO_DIG_PLL_OUT_ODD, 15, 1, 2),
+ F(6144000, P_LPASS_LPAAUDIO_DIG_PLL_OUT_ODD, 10, 1, 2),
+ F(8192000, P_LPASS_LPAAUDIO_DIG_PLL_OUT_ODD, 15, 0, 0),
+ F(9600000, P_BI_TCXO, 2, 0, 0),
+ F(12288000, P_LPASS_LPAAUDIO_DIG_PLL_OUT_ODD, 10, 0, 0),
+ F(19200000, P_BI_TCXO, 1, 0, 0),
+ F(24576000, P_LPASS_LPAAUDIO_DIG_PLL_OUT_ODD, 5, 0, 0),
+ { }
+};
+
+static struct clk_rcg2 ext_mclk0_clk_src = {
+ .cmd_rcgr = 0x20000,
+ .mnd_width = 8,
+ .hid_width = 5,
+ .parent_map = lpass_core_cc_parent_map_0,
+ .freq_tbl = ftbl_ext_mclk0_clk_src,
+ .clkr.hw.init = &(struct clk_init_data){
+ .name = "ext_mclk0_clk_src",
+ .parent_data = lpass_core_cc_parent_data_0,
+ .num_parents = 2,
+ .flags = CLK_SET_RATE_PARENT,
+ .ops = &clk_rcg2_ops,
+ },
+};
+
+static struct clk_rcg2 lpaif_pri_clk_src = {
+ .cmd_rcgr = 0x10000,
+ .mnd_width = 16,
+ .hid_width = 5,
+ .parent_map = lpass_core_cc_parent_map_0,
+ .freq_tbl = ftbl_ext_lpaif_clk_src,
+ .clkr.hw.init = &(struct clk_init_data){
+ .name = "lpaif_pri_clk_src",
+ .parent_data = lpass_core_cc_parent_data_0,
+ .num_parents = 2,
+ .flags = CLK_SET_RATE_PARENT,
+ .ops = &clk_rcg2_ops,
+ },
+};
+
+static struct clk_rcg2 lpaif_sec_clk_src = {
+ .cmd_rcgr = 0x11000,
+ .mnd_width = 16,
+ .hid_width = 5,
+ .parent_map = lpass_core_cc_parent_map_0,
+ .freq_tbl = ftbl_ext_lpaif_clk_src,
+ .clkr.hw.init = &(struct clk_init_data){
+ .name = "lpaif_sec_clk_src",
+ .parent_data = lpass_core_cc_parent_data_0,
+ .num_parents = 2,
+ .flags = CLK_SET_RATE_PARENT,
+ .ops = &clk_rcg2_ops,
+ },
+};
+
+static struct clk_branch lpass_audio_core_ext_mclk0_clk = {
+ .halt_reg = 0x20014,
+ .halt_check = BRANCH_HALT,
+ .hwcg_reg = 0x20014,
+ .hwcg_bit = 1,
+ .clkr = {
+ .enable_reg = 0x20014,
+ .enable_mask = BIT(0),
+ .hw.init = &(struct clk_init_data){
+ .name = "lpass_audio_core_ext_mclk0_clk",
+ .parent_data = &(const struct clk_parent_data){
+ .hw = &ext_mclk0_clk_src.clkr.hw,
+ },
+ .num_parents = 1,
+ .flags = CLK_SET_RATE_PARENT,
+ .ops = &clk_branch2_ops,
+ },
+ },
+};
+
+static struct clk_branch lpass_audio_core_lpaif_pri_ibit_clk = {
+ .halt_reg = 0x10018,
+ .halt_check = BRANCH_HALT,
+ .hwcg_reg = 0x10018,
+ .hwcg_bit = 1,
+ .clkr = {
+ .enable_reg = 0x10018,
+ .enable_mask = BIT(0),
+ .hw.init = &(struct clk_init_data){
+ .name = "lpass_audio_core_lpaif_pri_ibit_clk",
+ .parent_data = &(const struct clk_parent_data){
+ .hw = &lpaif_pri_clk_src.clkr.hw,
+ },
+ .num_parents = 1,
+ .flags = CLK_SET_RATE_PARENT,
+ .ops = &clk_branch2_ops,
+ },
+ },
+};
+
+static struct clk_branch lpass_audio_core_lpaif_sec_ibit_clk = {
+ .halt_reg = 0x11018,
+ .halt_check = BRANCH_HALT,
+ .hwcg_reg = 0x11018,
+ .hwcg_bit = 1,
+ .clkr = {
+ .enable_reg = 0x11018,
+ .enable_mask = BIT(0),
+ .hw.init = &(struct clk_init_data){
+ .name = "lpass_audio_core_lpaif_sec_ibit_clk",
+ .parent_data = &(const struct clk_parent_data){
+ .hw = &lpaif_sec_clk_src.clkr.hw,
+ },
+ .num_parents = 1,
+ .flags = CLK_SET_RATE_PARENT,
+ .ops = &clk_branch2_ops,
+ },
+ },
+};
+
+static struct clk_branch lpass_audio_core_sysnoc_mport_core_clk = {
+ .halt_reg = 0x23000,
+ .halt_check = BRANCH_HALT,
+ .hwcg_reg = 0x23000,
+ .hwcg_bit = 1,
+ .clkr = {
+ .enable_reg = 0x23000,
+ .enable_mask = BIT(0),
+ .hw.init = &(struct clk_init_data){
+ .name = "lpass_audio_core_sysnoc_mport_core_clk",
+ .parent_data = &(const struct clk_parent_data){
+ .hw = &core_clk_src.clkr.hw,
+ },
+ .num_parents = 1,
+ .flags = CLK_SET_RATE_PARENT,
+ .ops = &clk_branch2_ops,
+ },
+ },
+};
+
+static struct clk_regmap *lpass_core_cc_sc7180_clocks[] = {
+ [EXT_MCLK0_CLK_SRC] = &ext_mclk0_clk_src.clkr,
+ [LPAIF_PRI_CLK_SRC] = &lpaif_pri_clk_src.clkr,
+ [LPAIF_SEC_CLK_SRC] = &lpaif_sec_clk_src.clkr,
+ [CORE_CLK_SRC] = &core_clk_src.clkr,
+ [LPASS_AUDIO_CORE_EXT_MCLK0_CLK] = &lpass_audio_core_ext_mclk0_clk.clkr,
+ [LPASS_AUDIO_CORE_LPAIF_PRI_IBIT_CLK] =
+ &lpass_audio_core_lpaif_pri_ibit_clk.clkr,
+ [LPASS_AUDIO_CORE_LPAIF_SEC_IBIT_CLK] =
+ &lpass_audio_core_lpaif_sec_ibit_clk.clkr,
+ [LPASS_AUDIO_CORE_SYSNOC_MPORT_CORE_CLK] =
+ &lpass_audio_core_sysnoc_mport_core_clk.clkr,
+ [LPASS_LPAAUDIO_DIG_PLL] = &lpass_lpaaudio_dig_pll.clkr,
+ [LPASS_LPAAUDIO_DIG_PLL_OUT_ODD] = &lpass_lpaaudio_dig_pll_out_odd.clkr,
+};
+
+static struct gdsc lpass_pdc_hm_gdsc = {
+ .gdscr = 0x3090,
+ .pd = {
+ .name = "lpass_pdc_hm_gdsc",
+ },
+ .pwrsts = PWRSTS_OFF_ON,
+ .flags = VOTABLE,
+};
+
+static struct gdsc lpass_audio_hm_gdsc = {
+ .gdscr = 0x9090,
+ .pd = {
+ .name = "lpass_audio_hm_gdsc",
+ },
+ .pwrsts = PWRSTS_OFF_ON,
+};
+
+static struct gdsc lpass_core_hm_gdsc = {
+ .gdscr = 0x0,
+ .pd = {
+ .name = "lpass_core_hm_gdsc",
+ },
+ .pwrsts = PWRSTS_OFF_ON,
+ .flags = RETAIN_FF_ENABLE,
+};
+
+static struct gdsc *lpass_core_hm_sc7180_gdscs[] = {
+ [LPASS_CORE_HM_GDSCR] = &lpass_core_hm_gdsc,
+};
+
+static struct gdsc *lpass_audio_hm_sc7180_gdscs[] = {
+ [LPASS_PDC_HM_GDSCR] = &lpass_pdc_hm_gdsc,
+ [LPASS_AUDIO_HM_GDSCR] = &lpass_audio_hm_gdsc,
+};
+
+static struct regmap_config lpass_core_cc_sc7180_regmap_config = {
+ .reg_bits = 32,
+ .reg_stride = 4,
+ .val_bits = 32,
+ .fast_io = true,
+};
+
+static const struct qcom_cc_desc lpass_core_hm_sc7180_desc = {
+ .config = &lpass_core_cc_sc7180_regmap_config,
+ .gdscs = lpass_core_hm_sc7180_gdscs,
+ .num_gdscs = ARRAY_SIZE(lpass_core_hm_sc7180_gdscs),
+};
+
+static const struct qcom_cc_desc lpass_core_cc_sc7180_desc = {
+ .config = &lpass_core_cc_sc7180_regmap_config,
+ .clks = lpass_core_cc_sc7180_clocks,
+ .num_clks = ARRAY_SIZE(lpass_core_cc_sc7180_clocks),
+};
+
+static const struct qcom_cc_desc lpass_audio_hm_sc7180_desc = {
+ .config = &lpass_core_cc_sc7180_regmap_config,
+ .gdscs = lpass_audio_hm_sc7180_gdscs,
+ .num_gdscs = ARRAY_SIZE(lpass_audio_hm_sc7180_gdscs),
+};
+
+
+static int lpass_core_cc_sc7180_probe(struct platform_device *pdev)
+{
+ const struct qcom_cc_desc *desc;
+ struct regmap *regmap;
+ int ret;
+
+ lpass_core_cc_sc7180_regmap_config.name = "lpass_audio_cc";
+ desc = &lpass_audio_hm_sc7180_desc;
+ ret = qcom_cc_probe_by_index(pdev, 1, desc);
+ if (ret)
+ return ret;
+
+ lpass_core_cc_sc7180_regmap_config.name = "lpass_core_cc";
+ regmap = qcom_cc_map(pdev, &lpass_core_cc_sc7180_desc);
+ if (IS_ERR(regmap))
+ return PTR_ERR(regmap);
+
+ /*
+ * Keep the CLK always-ON
+ * LPASS_AUDIO_CORE_SYSNOC_SWAY_CORE_CLK
+ */
+ regmap_update_bits(regmap, 0x24000, BIT(0), BIT(0));
+
+ /* PLL settings */
+ regmap_write(regmap, 0x1008, 0x20);
+ regmap_update_bits(regmap, 0x1014, BIT(0), BIT(0));
+
+ clk_fabia_pll_configure(&lpass_lpaaudio_dig_pll, regmap,
+ &lpass_lpaaudio_dig_pll_config);
+
+ return qcom_cc_really_probe(pdev, &lpass_core_cc_sc7180_desc, regmap);
+}
+
+static int lpass_hm_core_probe(struct platform_device *pdev)
+{
+ const struct qcom_cc_desc *desc;
+ int ret;
+
+ lpass_core_cc_sc7180_regmap_config.name = "lpass_hm_core";
+ desc = &lpass_core_hm_sc7180_desc;
+
+ return qcom_cc_probe_by_index(pdev, 0, desc);
+}
+
+static const struct of_device_id lpass_core_cc_sc7180_match_table[] = {
+ {
+ .compatible = "qcom,sc7180-lpasshm",
+ .data = lpass_hm_core_probe,
+ },
+ {
+ .compatible = "qcom,sc7180-lpasscorecc",
+ .data = lpass_core_cc_sc7180_probe,
+ },
+ { }
+};
+MODULE_DEVICE_TABLE(of, lpass_core_cc_sc7180_match_table);
+
+static int lpass_core_sc7180_probe(struct platform_device *pdev)
+{
+ int (*clk_probe)(struct platform_device *p);
+ int ret;
+
+ pm_runtime_enable(&pdev->dev);
+ ret = pm_clk_create(&pdev->dev);
+ if (ret)
+ return ret;
+
+ ret = pm_clk_add(&pdev->dev, "gcc_lpass_sway");
+ if (ret < 0) {
+ dev_err(&pdev->dev, "failed to acquire iface clock\n");
+ goto disable_pm_runtime;
+ }
+
+ clk_probe = of_device_get_match_data(&pdev->dev);
+ if (!clk_probe)
+ return -EINVAL;
+
+ ret = clk_probe(pdev);
+ if (ret)
+ goto destroy_pm_clk;
+
+ return 0;
+
+destroy_pm_clk:
+ pm_clk_destroy(&pdev->dev);
+
+disable_pm_runtime:
+ pm_runtime_disable(&pdev->dev);
+
+ return ret;
+}
+
+static const struct dev_pm_ops lpass_core_cc_pm_ops = {
+ SET_RUNTIME_PM_OPS(pm_clk_suspend, pm_clk_resume, NULL)
+};
+
+static struct platform_driver lpass_core_cc_sc7180_driver = {
+ .probe = lpass_core_sc7180_probe,
+ .driver = {
+ .name = "lpass_core_cc-sc7180",
+ .of_match_table = lpass_core_cc_sc7180_match_table,
+ .pm = &lpass_core_cc_pm_ops,
+ },
+};
+
+static int __init lpass_core_cc_sc7180_init(void)
+{
+ return platform_driver_register(&lpass_core_cc_sc7180_driver);
+}
+subsys_initcall(lpass_core_cc_sc7180_init);
+
+static void __exit lpass_core_cc_sc7180_exit(void)
+{
+ platform_driver_unregister(&lpass_core_cc_sc7180_driver);
+}
+module_exit(lpass_core_cc_sc7180_exit);
+
+MODULE_DESCRIPTION("QTI LPASS_CORE_CC SC7180 Driver");
+MODULE_LICENSE("GPL v2");
--
Qualcomm INDIA, on behalf of Qualcomm Innovation Center, Inc.is a member
of the Code Aurora Forum, hosted by the Linux Foundation.
^ permalink raw reply related
* [PATCH v2 3/4] clk: qcom: gcc: Add support for GCC LPASS clock for SC7180
From: Taniya Das @ 2020-05-17 9:22 UTC (permalink / raw)
To: Stephen Boyd, Michael Turquette
Cc: David Brown, Rajendra Nayak, linux-arm-msm, linux-soc, linux-clk,
linux-kernel, Andy Gross, devicetree, robh, robh+dt, Taniya Das
In-Reply-To: <1589707344-8871-1-git-send-email-tdas@codeaurora.org>
Add the GCC lpass clock which is required to access the LPASS core
clocks.
Signed-off-by: Taniya Das <tdas@codeaurora.org>
---
drivers/clk/qcom/gcc-sc7180.c | 14 ++++++++++++++
1 file changed, 14 insertions(+)
diff --git a/drivers/clk/qcom/gcc-sc7180.c b/drivers/clk/qcom/gcc-sc7180.c
index 6a51b5b..d970647 100644
--- a/drivers/clk/qcom/gcc-sc7180.c
+++ b/drivers/clk/qcom/gcc-sc7180.c
@@ -2230,6 +2230,19 @@ static struct clk_branch gcc_mss_q6_memnoc_axi_clk = {
},
};
+static struct clk_branch gcc_lpass_cfg_noc_sway_clk = {
+ .halt_reg = 0x47018,
+ .halt_check = BRANCH_HALT_DELAY,
+ .clkr = {
+ .enable_reg = 0x47018,
+ .enable_mask = BIT(0),
+ .hw.init = &(struct clk_init_data){
+ .name = "gcc_lpass_cfg_noc_sway_clk",
+ .ops = &clk_branch2_ops,
+ },
+ },
+};
+
static struct gdsc ufs_phy_gdsc = {
.gdscr = 0x77004,
.pd = {
@@ -2406,6 +2419,7 @@ static struct clk_regmap *gcc_sc7180_clocks[] = {
[GCC_MSS_NAV_AXI_CLK] = &gcc_mss_nav_axi_clk.clkr,
[GCC_MSS_Q6_MEMNOC_AXI_CLK] = &gcc_mss_q6_memnoc_axi_clk.clkr,
[GCC_MSS_SNOC_AXI_CLK] = &gcc_mss_snoc_axi_clk.clkr,
+ [GCC_LPASS_CFG_NOC_SWAY_CLK] = &gcc_lpass_cfg_noc_sway_clk.clkr,
};
static const struct qcom_reset_map gcc_sc7180_resets[] = {
--
Qualcomm INDIA, on behalf of Qualcomm Innovation Center, Inc.is a member
of the Code Aurora Forum, hosted by the Linux Foundation.
^ permalink raw reply related
* [PATCH v2 1/4] clk: qcom: gdsc: Add support to enable retention of GSDCR
From: Taniya Das @ 2020-05-17 9:22 UTC (permalink / raw)
To: Stephen Boyd, Michael Turquette
Cc: David Brown, Rajendra Nayak, linux-arm-msm, linux-soc, linux-clk,
linux-kernel, Andy Gross, devicetree, robh, robh+dt, Taniya Das
In-Reply-To: <1589707344-8871-1-git-send-email-tdas@codeaurora.org>
Add support for the RETAIN_FF_ENABLE feature which enables the
usage of retention registers. These registers maintain their
state after disabling and re-enabling a GDSC.
Signed-off-by: Taniya Das <tdas@codeaurora.org>
---
drivers/clk/qcom/gdsc.c | 12 ++++++++++++
drivers/clk/qcom/gdsc.h | 1 +
2 files changed, 13 insertions(+)
diff --git a/drivers/clk/qcom/gdsc.c b/drivers/clk/qcom/gdsc.c
index a250f59..9983456 100644
--- a/drivers/clk/qcom/gdsc.c
+++ b/drivers/clk/qcom/gdsc.c
@@ -28,6 +28,7 @@
/* CFG_GDSCR */
#define GDSC_POWER_UP_COMPLETE BIT(16)
#define GDSC_POWER_DOWN_COMPLETE BIT(15)
+#define GDSC_RETAIN_FF_ENABLE BIT(11)
#define CFG_GDSCR_OFFSET 0x4
/* Wait 2^n CXO cycles between all states. Here, n=2 (4 cycles). */
@@ -202,6 +203,14 @@ static inline void gdsc_assert_reset_aon(struct gdsc *sc)
regmap_update_bits(sc->regmap, sc->clamp_io_ctrl,
GMEM_RESET_MASK, 0);
}
+
+static void gdsc_retain_ff_on(struct gdsc *sc)
+{
+ u32 mask = GDSC_RETAIN_FF_ENABLE;
+
+ regmap_update_bits(sc->regmap, sc->gdscr, mask, mask);
+}
+
static int gdsc_enable(struct generic_pm_domain *domain)
{
struct gdsc *sc = domain_to_gdsc(domain);
@@ -254,6 +263,9 @@ static int gdsc_enable(struct generic_pm_domain *domain)
udelay(1);
}
+ if (sc->flags & RETAIN_FF_ENABLE)
+ gdsc_retain_ff_on(sc);
+
return 0;
}
diff --git a/drivers/clk/qcom/gdsc.h b/drivers/clk/qcom/gdsc.h
index 64cdc8c..8604d44 100644
--- a/drivers/clk/qcom/gdsc.h
+++ b/drivers/clk/qcom/gdsc.h
@@ -49,6 +49,7 @@ struct gdsc {
#define AON_RESET BIT(4)
#define POLL_CFG_GDSCR BIT(5)
#define ALWAYS_ON BIT(6)
+#define RETAIN_FF_ENABLE BIT(7)
struct reset_controller_dev *rcdev;
unsigned int *resets;
unsigned int reset_count;
--
Qualcomm INDIA, on behalf of Qualcomm Innovation Center, Inc.is a member
of the Code Aurora Forum, hosted by the Linux Foundation.
^ permalink raw reply related
* [PATCH v2 2/4] dt-bindings: clock: Add YAML schemas for LPASS clocks on SC7180
From: Taniya Das @ 2020-05-17 9:22 UTC (permalink / raw)
To: Stephen Boyd, Michael Turquette
Cc: David Brown, Rajendra Nayak, linux-arm-msm, linux-soc, linux-clk,
linux-kernel, Andy Gross, devicetree, robh, robh+dt, Taniya Das
In-Reply-To: <1589707344-8871-1-git-send-email-tdas@codeaurora.org>
The LPASS(Low Power Audio Subsystem) clock provider have a bunch of generic
properties that are needed in a device tree. Also add clock ids for GCC
LPASS and LPASS Core clock IDs for LPASS client to request for the clocks.
Signed-off-by: Taniya Das <tdas@codeaurora.org>
---
.../bindings/clock/qcom,sc7180-lpasscorecc.yaml | 101 +++++++++++++++++++++
include/dt-bindings/clock/qcom,gcc-sc7180.h | 1 +
.../dt-bindings/clock/qcom,lpasscorecc-sc7180.h | 29 ++++++
3 files changed, 131 insertions(+)
create mode 100644 Documentation/devicetree/bindings/clock/qcom,sc7180-lpasscorecc.yaml
create mode 100644 include/dt-bindings/clock/qcom,lpasscorecc-sc7180.h
diff --git a/Documentation/devicetree/bindings/clock/qcom,sc7180-lpasscorecc.yaml b/Documentation/devicetree/bindings/clock/qcom,sc7180-lpasscorecc.yaml
new file mode 100644
index 0000000..c025a0ae
--- /dev/null
+++ b/Documentation/devicetree/bindings/clock/qcom,sc7180-lpasscorecc.yaml
@@ -0,0 +1,101 @@
+# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
+%YAML 1.2
+---
+$id: http://devicetree.org/schemas/clock/qcom,sc7180-lpasscorecc.yaml#
+$schema: http://devicetree.org/meta-schemas/core.yaml#
+
+title: Qualcomm LPASS Core Clock Controller Binding for SC7180
+
+maintainers:
+ - Taniya Das <tdas@codeaurora.org>
+
+description: |
+ Qualcomm LPASS core clock control module which supports the clocks and
+ power domains on SC7180.
+
+ See also:
+ - dt-bindings/clock/qcom,lpasscorecc-sc7180.h
+
+properties:
+ compatible:
+ enum:
+ - qcom,sc7180-lpasshm
+ - qcom,sc7180-lpasscorecc
+
+ clocks:
+ items:
+ - description: gcc_lpass_sway clock from GCC
+
+ clock-names:
+ items:
+ - const: gcc_lpass_sway
+
+ power-domains:
+ items:
+ - description: LPASS CORE HM GSDCR
+
+ '#clock-cells':
+ const: 1
+
+ '#power-domain-cells':
+ const: 1
+
+ reg:
+ minItems: 1
+ maxItems: 2
+ items:
+ - description: lpass audio cc register
+ - description: lpass core cc register
+
+ reg-names:
+ items:
+ - const: lpass_core_cc
+ - const: lpass_audio_cc
+
+if:
+ properties:
+ compatible:
+ contains:
+ const: qcom,sc7180-lpasshm
+then:
+ properties:
+ reg:
+ items:
+ - description: lpass hm core register
+
+required:
+ - compatible
+ - reg
+ - clocks
+ - clock-names
+ - '#clock-cells'
+ - '#power-domain-cells'
+
+additionalProperties: false
+
+examples:
+ - |
+ #include <dt-bindings/clock/qcom,gcc-sc7180.h>
+ #include <dt-bindings/clock/qcom,lpasscorecc-sc7180.h>
+ clock-controller@63000000 {
+ compatible = "qcom,sc7180-lpasshm";
+ reg = <0 0x63000000 0 0x28>;
+ clocks = <&gcc GCC_LPASS_CFG_NOC_SWAY_CLK>;
+ clock-names = "gcc_lpass_sway";
+ #clock-cells = <1>;
+ #power-domain-cells = <1>;
+ };
+
+ - |
+ clock-controller@62d00000 {
+ compatible = "qcom,sc7180-lpasscorecc";
+ reg = <0 0x62d00000 0 0x50000>,
+ <0 0x62780000 0 0x30000>;
+ reg-names = "lpass_core_cc", "lpass_audio_cc";
+ clocks = <&gcc GCC_LPASS_CFG_NOC_SWAY_CLK>;
+ clock-names = "gcc_lpass_sway";
+ power-domains = <&lpass_hm LPASS_CORE_HM_GDSCR>;
+ #clock-cells = <1>;
+ #power-domain-cells = <1>;
+ };
+...
diff --git a/include/dt-bindings/clock/qcom,gcc-sc7180.h b/include/dt-bindings/clock/qcom,gcc-sc7180.h
index 1258fd0..439476c 100644
--- a/include/dt-bindings/clock/qcom,gcc-sc7180.h
+++ b/include/dt-bindings/clock/qcom,gcc-sc7180.h
@@ -137,6 +137,7 @@
#define GCC_MSS_NAV_AXI_CLK 127
#define GCC_MSS_Q6_MEMNOC_AXI_CLK 128
#define GCC_MSS_SNOC_AXI_CLK 129
+#define GCC_LPASS_CFG_NOC_SWAY_CLK 130
/* GCC resets */
#define GCC_QUSB2PHY_PRIM_BCR 0
diff --git a/include/dt-bindings/clock/qcom,lpasscorecc-sc7180.h b/include/dt-bindings/clock/qcom,lpasscorecc-sc7180.h
new file mode 100644
index 0000000..a55d01d
--- /dev/null
+++ b/include/dt-bindings/clock/qcom,lpasscorecc-sc7180.h
@@ -0,0 +1,29 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+/*
+ * Copyright (c) 2020, The Linux Foundation. All rights reserved.
+ */
+
+#ifndef _DT_BINDINGS_CLK_QCOM_LPASS_CORE_CC_SC7180_H
+#define _DT_BINDINGS_CLK_QCOM_LPASS_CORE_CC_SC7180_H
+
+/* LPASS_CORE_CC clocks */
+#define LPASS_LPAAUDIO_DIG_PLL 0
+#define LPASS_LPAAUDIO_DIG_PLL_OUT_ODD 1
+#define CORE_CLK_SRC 2
+#define EXT_MCLK0_CLK_SRC 3
+#define LPAIF_PRI_CLK_SRC 4
+#define LPAIF_SEC_CLK_SRC 5
+#define LPASS_AUDIO_CORE_CORE_CLK 6
+#define LPASS_AUDIO_CORE_EXT_MCLK0_CLK 7
+#define LPASS_AUDIO_CORE_LPAIF_PRI_IBIT_CLK 8
+#define LPASS_AUDIO_CORE_LPAIF_SEC_IBIT_CLK 9
+#define LPASS_AUDIO_CORE_SYSNOC_MPORT_CORE_CLK 10
+
+/* LPASS Core power domains */
+#define LPASS_CORE_HM_GDSCR 0
+
+/* LPASS Audio power domains */
+#define LPASS_AUDIO_HM_GDSCR 0
+#define LPASS_PDC_HM_GDSCR 1
+
+#endif
--
Qualcomm INDIA, on behalf of Qualcomm Innovation Center, Inc.is a member
of the Code Aurora Forum, hosted by the Linux Foundation.
^ permalink raw reply related
* [PATCH v2 0/4] clk: qcom: Support for Low Power Audio Clocks on SC7180
From: Taniya Das @ 2020-05-17 9:22 UTC (permalink / raw)
To: Stephen Boyd, Michael Turquette
Cc: David Brown, Rajendra Nayak, linux-arm-msm, linux-soc, linux-clk,
linux-kernel, Andy Gross, devicetree, robh, robh+dt, Taniya Das
[v2]
* Update retention macro name.
* Update the register description in the documentation.
[v1]
* Add support for Retention of GDSCR.
* Add YAML schema for LPASS clocks and clock IDs for LPASS.
* Add clock driver for LPASS core clocks and GCC LPASS clock.
Taniya Das (4):
clk: qcom: gdsc: Add support to enable retention of GSDCR
dt-bindings: clock: Add YAML schemas for LPASS clocks on SC7180
clk: qcom: gcc: Add support for GCC LPASS clock for SC7180
clk: qcom: lpass: Add support for LPASS clock controller for SC7180
.../bindings/clock/qcom,sc7180-lpasscorecc.yaml | 101 +++++
drivers/clk/qcom/Kconfig | 9 +
drivers/clk/qcom/Makefile | 1 +
drivers/clk/qcom/gcc-sc7180.c | 14 +
drivers/clk/qcom/gdsc.c | 12 +
drivers/clk/qcom/gdsc.h | 1 +
drivers/clk/qcom/lpasscorecc-sc7180.c | 479 +++++++++++++++++++++
include/dt-bindings/clock/qcom,gcc-sc7180.h | 1 +
.../dt-bindings/clock/qcom,lpasscorecc-sc7180.h | 29 ++
9 files changed, 647 insertions(+)
create mode 100644 Documentation/devicetree/bindings/clock/qcom,sc7180-lpasscorecc.yaml
create mode 100644 drivers/clk/qcom/lpasscorecc-sc7180.c
create mode 100644 include/dt-bindings/clock/qcom,lpasscorecc-sc7180.h
--
Qualcomm INDIA, on behalf of Qualcomm Innovation Center, Inc.is a member
of the Code Aurora Forum, hosted by the Linux Foundation.
^ permalink raw reply
* Re: [PATCH v1 2/4] dt-bindings: clock: Add YAML schemas for LPASS clocks on SC7180
From: Taniya Das @ 2020-05-17 9:20 UTC (permalink / raw)
To: Rob Herring
Cc: Stephen Boyd, Michael Turquette, David Brown, Rajendra Nayak,
linux-arm-msm, linux-soc, linux-clk, linux-kernel, Andy Gross,
devicetree
In-Reply-To: <20200405020946.GA6110@bogus>
Hello Rob,
Thanks for the review.
On 4/5/2020 7:39 AM, Rob Herring wrote:
>> +
>> + reg:
>> + minItems: 1
>> + maxItems: 2
>
> Need to define what each one is when there are 2.
>
Yes will define them in the next patch.
>> +
>> +required:
>> + - compatible
>> + - reg
>> + - clocks
>> + - clock-names
>> + - '#clock-cells'
>> + - '#power-domain-cells'
>> +
>> +additionalProperties: false
>> +
>> +examples:
>> + - |
>> + #include <dt-bindings/clock/qcom,gcc-sc7180.h>
>> + #include <dt-bindings/clock/qcom,lpasscorecc-sc7180.h>
>> + clock-controller@63000000 {
>> + compatible = "qcom,sc7180-lpasshm";
>> + reg = <0 0x63000000 0 0x28>;
>> + clocks = <&gcc GCC_LPASS_CFG_NOC_SWAY_CLK>;
>> + clock-names = "gcc_lpass_sway";
>> + #clock-cells = <1>;
>> + #power-domain-cells = <1>;
>> + };
>> +
>> + - |
>> + clock-controller@62d00000 {
>> + compatible = "qcom,sc7180-lpasscorecc";
>> + reg = <0 0x62d00000 0 0x50000>,
>> + <0 0x62780000 0 0x30000>;
>> + clocks = <&gcc GCC_LPASS_CFG_NOC_SWAY_CLK>;
>> + clock-names = "gcc_lpass_sway";
>> + power-domains = <&lpass_hm LPASS_CORE_HM_GDSCR>;
>> + #clock-cells = <1>;
>> + #power-domain-cells = <1>;
>> + };
>> +...
> /* GCC resets */
>> #define GCC_QUSB2PHY_PRIM_BCR 0
>> diff --git a/include/dt-bindings/clock/qcom,lpasscorecc-sc7180.h b/include/dt-bindings/clock/qcom,lpasscorecc-sc7180.h
>> new file mode 100644
>> index 0000000..9466d5e
>> --- /dev/null
>> +++ b/include/dt-bindings/clock/qcom,lpasscorecc-sc7180.h
>> @@ -0,0 +1,28 @@
>> +/* SPDX-License-Identifier: GPL-2.0-only */
>> +/*
>> + * Copyright (c) 2020, The Linux Foundation. All rights reserved.
>> + */
>> +
>> +#ifndef _DT_BINDINGS_CLK_QCOM_LPASS_CORE_CC_SC7180_H
>> +#define _DT_BINDINGS_CLK_QCOM_LPASS_CORE_CC_SC7180_H
>> +
>> +/* LPASS_CORE_CC clocks */
>> +#define LPASS_LPAAUDIO_DIG_PLL 0
>> +#define LPASS_LPAAUDIO_DIG_PLL_OUT_ODD 1
>> +#define CORE_CLK_SRC 2
>> +#define EXT_MCLK0_CLK_SRC 3
>> +#define LPAIF_PRI_CLK_SRC 4
>> +#define LPAIF_SEC_CLK_SRC 5
>> +#define LPASS_AUDIO_CORE_CORE_CLK 6
>> +#define LPASS_AUDIO_CORE_EXT_MCLK0_CLK 7
>> +#define LPASS_AUDIO_CORE_LPAIF_PRI_IBIT_CLK 8
>> +#define LPASS_AUDIO_CORE_LPAIF_SEC_IBIT_CLK 9
>> +#define LPASS_AUDIO_CORE_SYSNOC_MPORT_CORE_CLK 10
>> +
>> +/* LPASS power domains */
>> +#define LPASS_CORE_HM_GDSCR 0
>> +
>> +#define LPASS_AUDIO_HM_GDSCR 0
>
> Kind of odd that 2 are the same value.
>
These GDSCs are from two different domains. I will update the comments
in the next patch.
>> +#define LPASS_PDC_HM_GDSCR 1
>> +
>> +#endif
>> --
>> Qualcomm INDIA, on behalf of Qualcomm Innovation Center, Inc.is a member
>> of the Code Aurora Forum, hosted by the Linux Foundation.
>>
--
QUALCOMM INDIA, on behalf of Qualcomm Innovation Center, Inc. is a member
of Code Aurora Forum, hosted by The Linux Foundation.
--
^ permalink raw reply
* Re: [PATCH v1 1/4] clk: qcom: gdsc: Add support to enable retention of GSDCR
From: Taniya Das @ 2020-05-17 9:17 UTC (permalink / raw)
To: Stephen Boyd, Michael Turquette
Cc: David Brown, Rajendra Nayak, linux-arm-msm, linux-soc, linux-clk,
linux-kernel, Andy Gross, devicetree, robh, robh+dt
In-Reply-To: <158646281555.77611.13094729241703720869@swboyd.mtv.corp.google.com>
Hello Stephen,
Thanks for the review.
On 4/10/2020 1:36 AM, Stephen Boyd wrote:
> Quoting Taniya Das (2020-03-27 12:48:02)
>> diff --git a/drivers/clk/qcom/gdsc.c b/drivers/clk/qcom/gdsc.c
>> index a250f59..cfe908f 100644
>> --- a/drivers/clk/qcom/gdsc.c
>> +++ b/drivers/clk/qcom/gdsc.c
>> @@ -28,6 +28,7 @@
>> /* CFG_GDSCR */
>> #define GDSC_POWER_UP_COMPLETE BIT(16)
>> #define GDSC_POWER_DOWN_COMPLETE BIT(15)
>> +#define GDSC_RETAIN_FF_ENABLE BIT(11)
>> #define CFG_GDSCR_OFFSET 0x4
>>
>> /* Wait 2^n CXO cycles between all states. Here, n=2 (4 cycles). */
>> @@ -202,6 +203,14 @@ static inline void gdsc_assert_reset_aon(struct gdsc *sc)
>> regmap_update_bits(sc->regmap, sc->clamp_io_ctrl,
>> GMEM_RESET_MASK, 0);
>> }
>> +
>> +static inline void gdsc_retain_ff_on(struct gdsc *sc)
>
> Drop inline please.
>
Will drop in the next patch.
>> +{
>> + u32 mask = RETAIN_FF_ENABLE;
>
> Is this supposed to be GDSC_RETAIN_FF_ENABLE?
>
Will update in next patch.
>> +
>> + regmap_update_bits(sc->regmap, sc->gdscr, mask, mask);
>> +}
>> +
>> static int gdsc_enable(struct generic_pm_domain *domain)
>> {
>> struct gdsc *sc = domain_to_gdsc(domain);
>> @@ -254,6 +263,9 @@ static int gdsc_enable(struct generic_pm_domain *domain)
>> udelay(1);
>> }
>>
>> + if (sc->flags & RETAIN_FF_ENABLE)
>> + gdsc_retain_ff_on(sc);
>> +
>> return 0;
>> }
>>
>> diff --git a/drivers/clk/qcom/gdsc.h b/drivers/clk/qcom/gdsc.h
>> index 64cdc8c..8604d44 100644
>> --- a/drivers/clk/qcom/gdsc.h
>> +++ b/drivers/clk/qcom/gdsc.h
>> @@ -49,6 +49,7 @@ struct gdsc {
>> #define AON_RESET BIT(4)
>> #define POLL_CFG_GDSCR BIT(5)
>> #define ALWAYS_ON BIT(6)
>> +#define RETAIN_FF_ENABLE BIT(7)
>
> This is a flag, not a register bit presumably.
Yes, it is a flag.
>
>> struct reset_controller_dev *rcdev;
>> unsigned int *resets;
>> unsigned int reset_count;
--
QUALCOMM INDIA, on behalf of Qualcomm Innovation Center, Inc. is a member
of Code Aurora Forum, hosted by The Linux Foundation.
--
^ permalink raw reply
* Re: [PATCH 05/12] gpu/drm: Ingenic: Fix opaque pointer casted to wrong type
From: Sam Ravnborg @ 2020-05-17 6:21 UTC (permalink / raw)
To: Paul Cercueil
Cc: David Airlie, Daniel Vetter, Rob Herring, Greg Kroah-Hartman,
Rafael J . Wysocki, devicetree, linux-kernel, stable, od,
dri-devel
In-Reply-To: <20200516215057.392609-5-paul@crapouillou.net>
On Sat, May 16, 2020 at 11:50:50PM +0200, Paul Cercueil wrote:
> The opaque pointer passed to the IRQ handler is a pointer to the
> drm_device, not a pointer to our ingenic_drm structure.
>
> It still worked, because our ingenic_drm structure contains the
> drm_device as its first field, so the pointer received had the same
> value, but this was not semantically correct.
>
> Cc: stable@vger.kernel.org # v5.3
> Fixes: 90b86fcc47b4 ("DRM: Add KMS driver for the Ingenic JZ47xx SoCs")
> Signed-off-by: Paul Cercueil <paul@crapouillou.net>
Acked-by: Sam Ravnborg <sam@ravnborg.org>
> ---
> drivers/gpu/drm/ingenic/ingenic-drm.c | 2 +-
> 1 file changed, 1 insertion(+), 1 deletion(-)
>
> diff --git a/drivers/gpu/drm/ingenic/ingenic-drm.c b/drivers/gpu/drm/ingenic/ingenic-drm.c
> index 0c472382a08b..97244462599b 100644
> --- a/drivers/gpu/drm/ingenic/ingenic-drm.c
> +++ b/drivers/gpu/drm/ingenic/ingenic-drm.c
> @@ -476,7 +476,7 @@ static int ingenic_drm_encoder_atomic_check(struct drm_encoder *encoder,
>
> static irqreturn_t ingenic_drm_irq_handler(int irq, void *arg)
> {
> - struct ingenic_drm *priv = arg;
> + struct ingenic_drm *priv = drm_device_get_priv(arg);
> unsigned int state;
>
> regmap_read(priv->map, JZ_REG_LCD_STATE, &state);
> --
> 2.26.2
>
> _______________________________________________
> dri-devel mailing list
> dri-devel@lists.freedesktop.org
> https://lists.freedesktop.org/mailman/listinfo/dri-devel
^ permalink raw reply
* Re: [PATCH 04/12] gpu/drm: ingenic: Fix bogus crtc_atomic_check callback
From: Sam Ravnborg @ 2020-05-17 6:17 UTC (permalink / raw)
To: Paul Cercueil
Cc: David Airlie, Daniel Vetter, Rob Herring, Greg Kroah-Hartman,
Rafael J . Wysocki, devicetree, linux-kernel, stable, od,
dri-devel
In-Reply-To: <20200516215057.392609-4-paul@crapouillou.net>
On Sat, May 16, 2020 at 11:50:49PM +0200, Paul Cercueil wrote:
> The code was comparing the SoC's maximum height with the mode's width,
> and vice-versa. D'oh.
>
> Cc: stable@vger.kernel.org # v5.6
> Fixes: a7c909b7c037 ("gpu/drm: ingenic: Check for display size in CRTC atomic check")
> Signed-off-by: Paul Cercueil <paul@crapouillou.net>
Looks correct.
Acked-by: Sam Ravnborg <sam@ravnborg.org>
> ---
>
> Notes:
> This patch was previously sent standalone.
> I marked it as superseded in patchwork.
> Nothing has been changed here.
>
> drivers/gpu/drm/ingenic/ingenic-drm.c | 4 ++--
> 1 file changed, 2 insertions(+), 2 deletions(-)
>
> diff --git a/drivers/gpu/drm/ingenic/ingenic-drm.c b/drivers/gpu/drm/ingenic/ingenic-drm.c
> index 632d72177123..0c472382a08b 100644
> --- a/drivers/gpu/drm/ingenic/ingenic-drm.c
> +++ b/drivers/gpu/drm/ingenic/ingenic-drm.c
> @@ -330,8 +330,8 @@ static int ingenic_drm_crtc_atomic_check(struct drm_crtc *crtc,
> if (!drm_atomic_crtc_needs_modeset(state))
> return 0;
>
> - if (state->mode.hdisplay > priv->soc_info->max_height ||
> - state->mode.vdisplay > priv->soc_info->max_width)
> + if (state->mode.hdisplay > priv->soc_info->max_width ||
> + state->mode.vdisplay > priv->soc_info->max_height)
> return -EINVAL;
>
> rate = clk_round_rate(priv->pix_clk,
> --
> 2.26.2
>
> _______________________________________________
> dri-devel mailing list
> dri-devel@lists.freedesktop.org
> https://lists.freedesktop.org/mailman/listinfo/dri-devel
^ permalink raw reply
* Re: [PATCH 02/12] dt-bindings: display: Add ingenic,ipu.yaml
From: Sam Ravnborg @ 2020-05-17 6:17 UTC (permalink / raw)
To: Paul Cercueil
Cc: David Airlie, Daniel Vetter, Rob Herring, Greg Kroah-Hartman,
Rafael J . Wysocki, devicetree, od, linux-kernel, dri-devel
In-Reply-To: <20200516215057.392609-2-paul@crapouillou.net>
Hi Paul.
On Sat, May 16, 2020 at 11:50:47PM +0200, Paul Cercueil wrote:
> Add documentation of the Device Tree bindings for the Image Processing
> Unit (IPU) found in most Ingenic SoCs.
>
> Signed-off-by: Paul Cercueil <paul@crapouillou.net>
For me it fails like this:
/Documentation/devicetree/bindings/display/ingenic,ipu.yaml: ignoring, error in schema: properties: compatible: oneOf: 1: items
warning: no schema found in file: Documentation/devicetree/bindings/display/ingenic,ipu.yaml
make[2]: *** [Documentation/devicetree/bindings/Makefile:42: Documentation/devicetree/bindings/processed-schema.yaml] Error 255
make[2]: *** Waiting for unfinished jobs....
Documentation/devicetree/bindings/display/ingenic,ipu.yaml: properties:compatible:oneOf:1:items: ['ingenic,jz4770-ipu', 'ingenic,jz4760-ipu'] is not valid under any of the given schemas (Possible causes of the failure):
Documentation/devicetree/bindings/display/ingenic,ipu.yaml: properties:compatible:oneOf:1:items: ['ingenic,jz4770-ipu', 'ingenic,jz4760-ipu'] is not of type 'object', 'boolean'
Documentation/devicetree/bindings/display/ingenic,ipu.yaml: properties:compatible:oneOf:1:items:0: 'ingenic,jz4770-ipu' is not of type 'object', 'boolean'
Documentation/devicetree/bindings/display/ingenic,ipu.yaml: properties:compatible:oneOf:1:items:1: 'ingenic,jz4760-ipu' is not of type 'object', 'boolean'
Sam
> ---
> .../bindings/display/ingenic,ipu.yaml | 65 +++++++++++++++++++
> 1 file changed, 65 insertions(+)
> create mode 100644 Documentation/devicetree/bindings/display/ingenic,ipu.yaml
>
> diff --git a/Documentation/devicetree/bindings/display/ingenic,ipu.yaml b/Documentation/devicetree/bindings/display/ingenic,ipu.yaml
> new file mode 100644
> index 000000000000..22fe02ca866d
> --- /dev/null
> +++ b/Documentation/devicetree/bindings/display/ingenic,ipu.yaml
> @@ -0,0 +1,65 @@
> +# SPDX-License-Identifier: (GPL-2.0 OR BSD-2-Clause)
> +%YAML 1.2
> +---
> +$id: http://devicetree.org/schemas/display/ingenic,ipu.yaml#
> +$schema: http://devicetree.org/meta-schemas/core.yaml#
> +
> +title: Ingenic SoCs Image Processing Unit (IPU) devicetree bindings
> +
> +maintainers:
> + - Paul Cercueil <paul@crapouillou.net>
> +
> +properties:
> + compatible:
> + oneOf:
> + - enum:
> + - ingenic,jz4725b-ipu
> + - ingenic,jz4760-ipu
> + - items:
> + - ingenic,jz4770-ipu
> + - ingenic,jz4760-ipu
> +
> + reg:
> + maxItems: 1
> +
> + interrupts:
> + maxItems: 1
> +
> + clocks:
> + maxItems: 1
> +
> + clock-names:
> + const: ipu
> +
> +patternProperties:
> + "^ports?$":
> + description: OF graph bindings (specified in bindings/graph.txt).
> +
> +required:
> + - compatible
> + - reg
> + - interrupts
> + - clocks
> + - clock-names
> +
> +additionalProperties: false
> +
> +examples:
> + - |
> + #include <dt-bindings/clock/jz4770-cgu.h>
> + ipu@13080000 {
> + compatible = "ingenic,jz4770-ipu", "ingenic,jz4760-ipu";
> + reg = <0x13080000 0x800>;
> +
> + interrupt-parent = <&intc>;
> + interrupts = <29>;
> +
> + clocks = <&cgu JZ4770_CLK_IPU>;
> + clock-names = "ipu";
> +
> + port {
> + ipu_ep: endpoint {
> + remote-endpoint = <&lcdc_ep>;
> + };
> + };
> + };
> --
> 2.26.2
>
> _______________________________________________
> dri-devel mailing list
> dri-devel@lists.freedesktop.org
> https://lists.freedesktop.org/mailman/listinfo/dri-devel
^ permalink raw reply
* Re: [PATCH 01/12] dt-bindings: display: Convert ingenic,lcd.txt to YAML
From: Sam Ravnborg @ 2020-05-17 6:12 UTC (permalink / raw)
To: Paul Cercueil
Cc: David Airlie, Daniel Vetter, Rob Herring, Greg Kroah-Hartman,
Rafael J . Wysocki, devicetree, od, linux-kernel, dri-devel
In-Reply-To: <20200516215057.392609-1-paul@crapouillou.net>
Hi PAul.
On Sat, May 16, 2020 at 11:50:46PM +0200, Paul Cercueil wrote:
> Convert the ingenic,lcd.txt to a new ingenic,lcd.yaml file.
>
> In the process, the new ingenic,jz4780-lcd compatible string has been
> added.
>
> Signed-off-by: Paul Cercueil <paul@crapouillou.net>
Looks good.
Had to read the minItems/maxItems thing twice to get it,
so I also played a little with the examples, and it worked as expected.
Acked-by: Sam Ravnborg <sam@ravnborg.org>
> ---
>
> Notes:
> This patch comes from a different patchset so it's effectively a V2.
>
> Changes were:
> - lcd_pclk and lcd clocks are in the correct order now,
> - Add 'port' and 'ports' properties, and document the valid ports.
>
> .../bindings/display/ingenic,lcd.txt | 45 -------
> .../bindings/display/ingenic,lcd.yaml | 126 ++++++++++++++++++
> 2 files changed, 126 insertions(+), 45 deletions(-)
> delete mode 100644 Documentation/devicetree/bindings/display/ingenic,lcd.txt
> create mode 100644 Documentation/devicetree/bindings/display/ingenic,lcd.yaml
>
> diff --git a/Documentation/devicetree/bindings/display/ingenic,lcd.txt b/Documentation/devicetree/bindings/display/ingenic,lcd.txt
> deleted file mode 100644
> index 01e3261defb6..000000000000
> --- a/Documentation/devicetree/bindings/display/ingenic,lcd.txt
> +++ /dev/null
> @@ -1,45 +0,0 @@
> -Ingenic JZ47xx LCD driver
> -
> -Required properties:
> -- compatible: one of:
> - * ingenic,jz4740-lcd
> - * ingenic,jz4725b-lcd
> - * ingenic,jz4770-lcd
> -- reg: LCD registers location and length
> -- clocks: LCD pixclock and device clock specifiers.
> - The device clock is only required on the JZ4740.
> -- clock-names: "lcd_pclk" and "lcd"
> -- interrupts: Specifies the interrupt line the LCD controller is connected to.
> -
> -Example:
> -
> -panel {
> - compatible = "sharp,ls020b1dd01d";
> -
> - backlight = <&backlight>;
> - power-supply = <&vcc>;
> -
> - port {
> - panel_input: endpoint {
> - remote-endpoint = <&panel_output>;
> - };
> - };
> -};
> -
> -
> -lcd: lcd-controller@13050000 {
> - compatible = "ingenic,jz4725b-lcd";
> - reg = <0x13050000 0x1000>;
> -
> - interrupt-parent = <&intc>;
> - interrupts = <31>;
> -
> - clocks = <&cgu JZ4725B_CLK_LCD>;
> - clock-names = "lcd";
> -
> - port {
> - panel_output: endpoint {
> - remote-endpoint = <&panel_input>;
> - };
> - };
> -};
> diff --git a/Documentation/devicetree/bindings/display/ingenic,lcd.yaml b/Documentation/devicetree/bindings/display/ingenic,lcd.yaml
> new file mode 100644
> index 000000000000..d56db1802fad
> --- /dev/null
> +++ b/Documentation/devicetree/bindings/display/ingenic,lcd.yaml
> @@ -0,0 +1,126 @@
> +# SPDX-License-Identifier: (GPL-2.0 OR BSD-2-Clause)
> +%YAML 1.2
> +---
> +$id: http://devicetree.org/schemas/display/ingenic,lcd.yaml#
> +$schema: http://devicetree.org/meta-schemas/core.yaml#
> +
> +title: Ingenic SoCs LCD controller devicetree bindings
> +
> +maintainers:
> + - Paul Cercueil <paul@crapouillou.net>
> +
> +properties:
> + $nodename:
> + pattern: "^lcd-controller@[0-9a-f]+$"
> +
> + compatible:
> + enum:
> + - ingenic,jz4740-lcd
> + - ingenic,jz4725b-lcd
> + - ingenic,jz4770-lcd
> + - ingenic,jz4780-lcd
> +
> + reg:
> + maxItems: 1
> +
> + interrupts:
> + maxItems: 1
> +
> + clocks:
> + items:
> + - description: Pixel clock
> + - description: Module clock
> + minItems: 1
> +
> + clock-names:
> + items:
> + - const: lcd_pclk
> + - const: lcd
> + minItems: 1
> +
> + port:
> + description: OF graph bindings (specified in bindings/graph.txt).
> +
> + ports:
> + description: OF graph bindings (specified in bindings/graph.txt).
> + type: object
> + properties:
> + port@0:
> + type: object
> + description: DPI output, to interface with TFT panels.
> +
> + port@8:
> + type: object
> + description: Link to the Image Processing Unit (IPU).
> + (See ingenic,ipu.yaml).
> +
> + required:
> + - port@0
> +
> +required:
> + - compatible
> + - reg
> + - interrupts
> + - clocks
> + - clock-names
> +
> +if:
> + properties:
> + compatible:
> + contains:
> + enum:
> + - ingenic,jz4740-lcd
> + - ingenic,jz4780-lcd
> +then:
> + properties:
> + clocks:
> + minItems: 2
> + clock-names:
> + minItems: 2
> +else:
> + properties:
> + clocks:
> + maxItems: 1
> + clock-names:
> + maxItems: 1
> +
> +additionalProperties: false
> +
> +examples:
> + - |
> + #include <dt-bindings/clock/jz4740-cgu.h>
> + lcd-controller@13050000 {
> + compatible = "ingenic,jz4740-lcd";
> + reg = <0x13050000 0x1000>;
> +
> + interrupt-parent = <&intc>;
> + interrupts = <30>;
> +
> + clocks = <&cgu JZ4740_CLK_LCD_PCLK>, <&cgu JZ4740_CLK_LCD>;
> + clock-names = "lcd_pclk", "lcd";
> +
> + port {
> + endpoint {
> + remote-endpoint = <&panel_input>;
> + };
> + };
> + };
> +
> + - |
> + #include <dt-bindings/clock/jz4725b-cgu.h>
> + lcd-controller@13050000 {
> + compatible = "ingenic,jz4725b-lcd";
> + reg = <0x13050000 0x1000>;
> +
> + interrupt-parent = <&intc>;
> + interrupts = <31>;
> +
> + clocks = <&cgu JZ4725B_CLK_LCD>;
> + clock-names = "lcd_pclk";
> +
> + port {
> + endpoint {
> + remote-endpoint = <&panel_input>;
> + };
> + };
> + };
> --
> 2.26.2
>
> _______________________________________________
> dri-devel mailing list
> dri-devel@lists.freedesktop.org
> https://lists.freedesktop.org/mailman/listinfo/dri-devel
^ permalink raw reply
* Re: [PATCH v9 4/4] media: i2c: Add RDACM20 driver
From: Sakari Ailus @ 2020-05-16 22:36 UTC (permalink / raw)
To: Kieran Bingham
Cc: linux-renesas-soc, linux-media, devicetree, linux-kernel,
Mauro Carvalho Chehab, Kieran Bingham, Laurent Pinchart,
Jacopo Mondi, Niklas Söderlund, Hans Verkuil, Hyun Kwon,
Manivannan Sadhasivam, Rob Herring, Jacopo Mondi,
Laurent Pinchart, Niklas Söderlund, Rob Herring
In-Reply-To: <20200512155105.1068064-5-kieran.bingham+renesas@ideasonboard.com>
Hi Kieran,
On Tue, May 12, 2020 at 04:51:05PM +0100, Kieran Bingham wrote:
> From: Jacopo Mondi <jacopo+renesas@jmondi.org>
>
> The RDACM20 is a GMSL camera supporting 1280x800 resolution images
> developed by IMI based on an Omnivision 10635 sensor and a Maxim MAX9271
> GMSL serializer.
>
> The GMSL link carries power, control (I2C) and video data over a
> single coax cable.
>
> Signed-off-by: Jacopo Mondi <jacopo+renesas@jmondi.org>
> Signed-off-by: Laurent Pinchart <laurent.pinchart+renesas@ideasonboard.com>
> Signed-off-by: Niklas Söderlund <niklas.soderlund+renesas@ragnatech.se>
> Signed-off-by: Kieran Bingham <kieran.bingham+renesas@ideasonboard.com>
> Reviewed-by: Rob Herring <robh@kernel.org>
>
> ---
> v2:
> - Fix MAINTAINERS entry
>
> v3:
> - Use new V4L2_MBUS_CSI2_DPHY bus type
> - Remove 'always zero' error print
> - Fix module description
>
> v5:
> - use sleep rather than busy loops for 10 ms delays
> - Return ov10635_set_regs directly
> - Use devm_kzalloc instead of kzalloc in probe()
> - Or in the flags: dev->sd.flags |= V4L2_SUBDEV_FL_HAS_DEVNODE
> - Ensure v4l2_ctrl_handler_free() is called
> - rdacm20_probe converted to use .probe_new and drop i2c device id
> tables
> - Remove rdacm20_g_mbus_config
>
> v7:
> - Use new i2c_new_dummy_device API
>
> v8:
> - Almost a new version, main changes are:
> - Split max9271 out
> - Rework bindings to yaml format and include forthcoming RDACM21
> - Remove unecessary header file
> - Add pixel rate calculation
> - Drop unused fields and dead code
> - Rebase on media-master v5.7-rc1
> - A detailed list of changes provided as fixup patches is avilable at
> git://jmondi.org/linux #gmsl/jmondi/media-master/max9286-v8+old-rdacm20+split
>
> which includes the following list of patches:
> squash!: rdacm20: Report pixel rate
> squash!: rdacm20: Force sensor ID re-read
> squash!: rdamc20: Check register programming result
> squash!: rdacm20: Update to use the new max9271 APIs
> squash!: rdacm20: Start with serial link disabled
> squash!: rdacm20: Hard reset the sensor
> squash!: rdacm20: Cache i2c addresses
> squash!: rdacm20: Adjust to use new max9271 API
> squash!: rdacm20: Drop unused format definition
> squash!: rdacm20: Refresh file header
> squash!: max9271: Add two functions to control addresses
> squash!: max9271: Do not rewrite i2c address
> squash!: max9271: Improve gpio handling
> squash!: max9271: Rework core functions
> squash!: max9271 refresh
> squash!: rdacm20: Update maintainers file
> squash!: rdacm20: Remove unsued/duplicated defines
> squash!: rdacm20: Remove header file
> squash!: rdacm20: Move content of header into C file
> squash!: rdacm20: Reorganize register table
> squash!: rdacm20: Fix subdevice flag creation
> squash!: rdacm20: Drop i2c id table
> squash!: rdacm20: Release control handler
> squash!: rdacm20: Better handle i2c dummy error
> squash!: rdacm20: Use devm_kzalloc
> squash!: rdacm20: Use probe_new()
> squash!: rdacm20: Drop deprecated g_mbus_config
> media: i2c: rdacm20: Break max9271 out from rdacm20
> squash!: rdacm20: Rebase on latest media/maste
> media: i2c: Add RDACM20 driver
> dt-bindings: media: i2c: Add bindings for IMI RDACM2x
>
> v9:
> - Drop OV10635_FORMAT and use MEDIA_BUS_FMT_UYVY8_2X8 as suggested by
> Hans
>
> MAINTAINERS | 12 +
> drivers/media/i2c/Kconfig | 13 +
> drivers/media/i2c/Makefile | 2 +
> drivers/media/i2c/max9271.c | 341 ++++++++++++++++++
> drivers/media/i2c/max9271.h | 224 ++++++++++++
> drivers/media/i2c/rdacm20.c | 667 ++++++++++++++++++++++++++++++++++++
> 6 files changed, 1259 insertions(+)
> create mode 100644 drivers/media/i2c/max9271.c
> create mode 100644 drivers/media/i2c/max9271.h
> create mode 100644 drivers/media/i2c/rdacm20.c
>
> diff --git a/MAINTAINERS b/MAINTAINERS
> index 99e3bf7760fd..713b56652181 100644
> --- a/MAINTAINERS
> +++ b/MAINTAINERS
> @@ -14206,6 +14206,18 @@ S: Supported
> T: git git://git.kernel.org/pub/scm/linux/kernel/git/paulmck/linux-rcu.git dev
> F: tools/testing/selftests/rcutorture
>
> +RDACM20 Camera Sensor
> +M: Jacopo Mondi <jacopo+renesas@jmondi.org>
> +M: Kieran Bingham <kieran.bingham+renesas@ideasonboard.com>
> +M: Laurent Pinchart <laurent.pinchart+renesas@ideasonboard.com>
> +M: Niklas Söderlund <niklas.soderlund+renesas@ragnatech.se>
> +L: linux-media@vger.kernel.org
> +S: Maintained
> +F: Documentation/devicetree/bindings/media/i2c/imi,rdacm2x-gmsl.yaml
> +F: drivers/media/i2c/rdacm20.c
> +F: drivers/media/i2c/max9271.c
> +F: drivers/media/i2c/max9271.h
> +
> RDC R-321X SoC
> M: Florian Fainelli <florian@openwrt.org>
> S: Maintained
> diff --git a/drivers/media/i2c/Kconfig b/drivers/media/i2c/Kconfig
> index 2e390f41f6da..6e3300760c7f 100644
> --- a/drivers/media/i2c/Kconfig
> +++ b/drivers/media/i2c/Kconfig
> @@ -1157,6 +1157,19 @@ config VIDEO_NOON010PC30
>
> source "drivers/media/i2c/m5mols/Kconfig"
>
> +config VIDEO_RDACM20
> + tristate "IMI RDACM20 camera support"
> + depends on I2C
> + select V4L2_FWNODE
> + select VIDEO_V4L2_SUBDEV_API
> + select MEDIA_CONTROLLER
> + help
> + This driver supports the IMI RDACM20 GMSL camera, used in
> + ADAS systems.
> +
> + This camera should be used in conjunction with a GMSL
> + deserialiser such as the MAX9286.
> +
> config VIDEO_RJ54N1
> tristate "Sharp RJ54N1CB0C sensor support"
> depends on I2C && VIDEO_V4L2
> diff --git a/drivers/media/i2c/Makefile b/drivers/media/i2c/Makefile
> index f0b001ee4b05..64e6df50f68c 100644
> --- a/drivers/media/i2c/Makefile
> +++ b/drivers/media/i2c/Makefile
> @@ -118,6 +118,8 @@ obj-$(CONFIG_VIDEO_IMX290) += imx290.o
> obj-$(CONFIG_VIDEO_IMX319) += imx319.o
> obj-$(CONFIG_VIDEO_IMX355) += imx355.o
> obj-$(CONFIG_VIDEO_MAX9286) += max9286.o
> +rdacm20-camera_module-objs := rdacm20.o max9271.o
> +obj-$(CONFIG_VIDEO_RDACM20) += rdacm20-camera_module.o
> obj-$(CONFIG_VIDEO_ST_MIPID02) += st-mipid02.o
>
> obj-$(CONFIG_SDR_MAX2175) += max2175.o
> diff --git a/drivers/media/i2c/max9271.c b/drivers/media/i2c/max9271.c
> new file mode 100644
> index 000000000000..0f6f7a092a46
> --- /dev/null
> +++ b/drivers/media/i2c/max9271.c
> @@ -0,0 +1,341 @@
> +// SPDX-License-Identifier: GPL-2.0+
> +/*
> + * Copyright (C) 2017-2020 Jacopo Mondi
> + * Copyright (C) 2017-2020 Kieran Bingham
> + * Copyright (C) 2017-2020 Laurent Pinchart
> + * Copyright (C) 2017-2020 Niklas Söderlund
> + * Copyright (C) 2016 Renesas Electronics Corporation
> + * Copyright (C) 2015 Cogent Embedded, Inc.
> + *
> + * This file exports functions to control the Maxim MAX9271 GMSL serializer
> + * chip. This is not a self-contained driver, as MAX9271 is usually embedded in
> + * camera modules with at least one image sensor and optional additional
> + * components, such as uController units or ISPs/DSPs.
> + *
> + * Drivers for the camera modules (i.e. rdacm20/21) are expected to use
> + * functions exported from this library driver to maximize code re-use.
> + */
> +
> +#include <linux/delay.h>
> +#include <linux/i2c.h>
> +
> +#include "max9271.h"
> +
> +static int max9271_read(struct max9271_device *dev, u8 reg)
> +{
> + int ret;
> +
> + dev_dbg(&dev->client->dev, "%s(0x%02x)\n", __func__, reg);
> +
> + ret = i2c_smbus_read_byte_data(dev->client, reg);
> + if (ret < 0)
> + dev_dbg(&dev->client->dev,
> + "%s: register 0x%02x read failed (%d)\n",
> + __func__, reg, ret);
> +
> + return ret;
> +}
> +
> +static int max9271_write(struct max9271_device *dev, u8 reg, u8 val)
> +{
> + int ret;
> +
> + dev_dbg(&dev->client->dev, "%s(0x%02x, 0x%02x)\n", __func__, reg, val);
> +
> + ret = i2c_smbus_write_byte_data(dev->client, reg, val);
> + if (ret < 0)
> + dev_err(&dev->client->dev,
> + "%s: register 0x%02x write failed (%d)\n",
> + __func__, reg, ret);
> +
> + return ret;
> +}
> +
> +/*
> + * max9271_pclk_detect() - Detect valid pixel clock from image sensor
> + *
> + * Wait up to 10ms for a valid pixel clock.
> + *
> + * Returns 0 for success, < 0 for pixel clock not properly detected
> + */
> +static int max9271_pclk_detect(struct max9271_device *dev)
> +{
> + unsigned int i;
> + int ret;
> +
> + for (i = 0; i < 100; i++) {
> + ret = max9271_read(dev, 0x15);
> + if (ret < 0)
> + return ret;
> +
> + if (ret & MAX9271_PCLKDET)
> + return 0;
> +
> + usleep_range(50, 100);
> + }
> +
> + dev_err(&dev->client->dev, "Unable to detect valid pixel clock\n");
> +
> + return -EIO;
> +}
> +
> +int max9271_set_serial_link(struct max9271_device *dev, bool enable)
> +{
> + int ret;
> + u8 val = MAX9271_REVCCEN | MAX9271_FWDCCEN;
> +
> + if (enable) {
> + ret = max9271_pclk_detect(dev);
> + if (ret)
> + return ret;
> +
> + val |= MAX9271_SEREN;
> + } else {
> + val |= MAX9271_CLINKEN;
> + }
> +
> + /*
> + * The serializer temporarily disables the reverse control channel for
> + * 350µs after starting/stopping the forward serial link, but the
> + * deserializer synchronization time isn't clearly documented.
> + *
> + * According to the serializer datasheet we should wait 3ms, while
> + * according to the deserializer datasheet we should wait 5ms.
> + *
> + * Short delays here appear to show bit-errors in the writes following.
> + * Therefore a conservative delay seems best here.
> + */
> + max9271_write(dev, 0x04, val);
> + usleep_range(5000, 8000);
> +
> + return 0;
> +}
> +EXPORT_SYMBOL_GPL(max9271_set_serial_link);
> +
> +int max9271_configure_i2c(struct max9271_device *dev, u8 i2c_config)
> +{
> + int ret;
> +
> + ret = max9271_write(dev, 0x0d, i2c_config);
> + if (ret)
> + return ret;
> +
> + /* The delay required after an I2C bus configuration change is not
> + * characterized in the serializer manual. Sleep up to 5msec to
> + * stay safe.
> + */
> + usleep_range(3500, 5000);
> +
> + return 0;
> +}
> +EXPORT_SYMBOL_GPL(max9271_configure_i2c);
> +
> +int max9271_set_high_threshold(struct max9271_device *dev, bool enable)
> +{
> + int ret;
> +
> + ret = max9271_read(dev, 0x08);
> + if (ret < 0)
> + return ret;
> +
> + /*
> + * Enable or disable reverse channel high threshold to increase
> + * immunity to power supply noise.
> + */
> + max9271_write(dev, 0x08, enable ? ret | BIT(0) : ret & ~BIT(0));
> + usleep_range(2000, 2500);
> +
> + return 0;
> +}
> +EXPORT_SYMBOL_GPL(max9271_set_high_threshold);
> +
> +int max9271_configure_gmsl_link(struct max9271_device *dev)
> +{
> + /*
> + * Configure the GMSL link:
> + *
> + * - Double input mode, high data rate, 24-bit mode
> + * - Latch input data on PCLKIN rising edge
> + * - Enable HS/VS encoding
> + * - 1-bit parity error detection
> + *
> + * TODO: Make the GMSL link configuration parametric.
> + */
> + max9271_write(dev, 0x07, MAX9271_DBL | MAX9271_HVEN |
> + MAX9271_EDC_1BIT_PARITY);
> + usleep_range(5000, 8000);
> +
> + /*
> + * Adjust spread spectrum to +4% and auto-detect pixel clock
> + * and serial link rate.
> + */
> + max9271_write(dev, 0x02, MAX9271_SPREAD_SPECT_4 | MAX9271_R02_RES |
> + MAX9271_PCLK_AUTODETECT | MAX9271_SERIAL_AUTODETECT);
> + usleep_range(5000, 8000);
> +
> + return 0;
> +}
> +EXPORT_SYMBOL_GPL(max9271_configure_gmsl_link);
> +
> +int max9271_set_gpios(struct max9271_device *dev, u8 gpio_mask)
> +{
> + int ret;
> +
> + ret = max9271_read(dev, 0x0f);
> + if (ret < 0)
> + return 0;
> +
> + ret |= gpio_mask;
> + ret = max9271_write(dev, 0x0f, ret);
> + if (ret < 0) {
> + dev_err(&dev->client->dev, "Failed to set gpio (%d)\n", ret);
> + return ret;
> + }
> +
> + usleep_range(3500, 5000);
> +
> + return 0;
> +}
> +EXPORT_SYMBOL_GPL(max9271_set_gpios);
> +
> +int max9271_clear_gpios(struct max9271_device *dev, u8 gpio_mask)
> +{
> + int ret;
> +
> + ret = max9271_read(dev, 0x0f);
> + if (ret < 0)
> + return 0;
> +
> + ret &= ~gpio_mask;
> + ret = max9271_write(dev, 0x0f, ret);
> + if (ret < 0) {
> + dev_err(&dev->client->dev, "Failed to clear gpio (%d)\n", ret);
> + return ret;
> + }
> +
> + usleep_range(3500, 5000);
> +
> + return 0;
> +}
> +EXPORT_SYMBOL_GPL(max9271_clear_gpios);
> +
> +int max9271_enable_gpios(struct max9271_device *dev, u8 gpio_mask)
> +{
> + int ret;
> +
> + ret = max9271_read(dev, 0x0f);
> + if (ret < 0)
> + return 0;
> +
> + /* BIT(0) reserved: GPO is always enabled. */
> + ret |= gpio_mask | BIT(0);
> + ret = max9271_write(dev, 0x0e, ret);
> + if (ret < 0) {
> + dev_err(&dev->client->dev, "Failed to enable gpio (%d)\n", ret);
> + return ret;
> + }
> +
> + usleep_range(3500, 5000);
> +
> + return 0;
> +}
> +EXPORT_SYMBOL_GPL(max9271_enable_gpios);
> +
> +int max9271_disable_gpios(struct max9271_device *dev, u8 gpio_mask)
> +{
> + int ret;
> +
> + ret = max9271_read(dev, 0x0f);
> + if (ret < 0)
> + return 0;
> +
> + /* BIT(0) reserved: GPO cannot be disabled */
> + ret &= (~gpio_mask | BIT(0));
> + ret = max9271_write(dev, 0x0e, ret);
> + if (ret < 0) {
> + dev_err(&dev->client->dev, "Failed to disable gpio (%d)\n", ret);
> + return ret;
> + }
> +
> + usleep_range(3500, 5000);
> +
> + return 0;
> +}
> +EXPORT_SYMBOL_GPL(max9271_disable_gpios);
> +
> +int max9271_verify_id(struct max9271_device *dev)
> +{
> + int ret;
> +
> + ret = max9271_read(dev, 0x1e);
> + if (ret < 0) {
> + dev_err(&dev->client->dev, "MAX9271 ID read failed (%d)\n",
> + ret);
> + return ret;
> + }
> +
> + if (ret != MAX9271_ID) {
> + dev_err(&dev->client->dev, "MAX9271 ID mismatch (0x%02x)\n",
> + ret);
> + return -ENXIO;
> + }
> +
> + return 0;
> +}
> +EXPORT_SYMBOL_GPL(max9271_verify_id);
> +
> +int max9271_set_address(struct max9271_device *dev, u8 addr)
> +{
> + int ret;
> +
> + ret = max9271_write(dev, 0x00, addr << 1);
> + if (ret < 0) {
> + dev_err(&dev->client->dev,
> + "MAX9271 I2C address change failed (%d)\n", ret);
> + return ret;
> + }
> + usleep_range(3500, 5000);
> +
> + return 0;
> +}
> +EXPORT_SYMBOL_GPL(max9271_set_address);
> +
> +int max9271_set_deserializer_address(struct max9271_device *dev, u8 addr)
> +{
> + int ret;
> +
> + ret = max9271_write(dev, 0x01, addr << 1);
> + if (ret < 0) {
> + dev_err(&dev->client->dev,
> + "MAX9271 deserializer address set failed (%d)\n", ret);
> + return ret;
> + }
> + usleep_range(3500, 5000);
> +
> + return 0;
> +}
> +EXPORT_SYMBOL_GPL(max9271_set_deserializer_address);
> +
> +int max9271_set_translation(struct max9271_device *dev, u8 source, u8 dest)
> +{
> + int ret;
> +
> + ret = max9271_write(dev, 0x09, source << 1);
> + if (ret < 0) {
> + dev_err(&dev->client->dev,
> + "MAX9271 I2C translation setup failed (%d)\n", ret);
> + return ret;
> + }
> + usleep_range(3500, 5000);
> +
> + ret = max9271_write(dev, 0x0a, dest << 1);
> + if (ret < 0) {
> + dev_err(&dev->client->dev,
> + "MAX9271 I2C translation setup failed (%d)\n", ret);
> + return ret;
> + }
> + usleep_range(3500, 5000);
> +
> + return 0;
> +}
> +EXPORT_SYMBOL_GPL(max9271_set_translation);
> diff --git a/drivers/media/i2c/max9271.h b/drivers/media/i2c/max9271.h
> new file mode 100644
> index 000000000000..d78fb21441e9
> --- /dev/null
> +++ b/drivers/media/i2c/max9271.h
> @@ -0,0 +1,224 @@
> +/* SPDX-License-Identifier: GPL-2.0+ */
> +/*
> + * Copyright (C) 2017-2020 Jacopo Mondi
> + * Copyright (C) 2017-2020 Kieran Bingham
> + * Copyright (C) 2017-2020 Laurent Pinchart
> + * Copyright (C) 2017-2020 Niklas Söderlund
> + * Copyright (C) 2016 Renesas Electronics Corporation
> + * Copyright (C) 2015 Cogent Embedded, Inc.
> + */
> +
> +#include <linux/i2c.h>
> +
> +#define MAX9271_DEFAULT_ADDR 0x40
> +
> +/* Register 0x02 */
> +#define MAX9271_SPREAD_SPECT_0 (0 << 5)
> +#define MAX9271_SPREAD_SPECT_05 (1 << 5)
> +#define MAX9271_SPREAD_SPECT_15 (2 << 5)
> +#define MAX9271_SPREAD_SPECT_1 (5 << 5)
> +#define MAX9271_SPREAD_SPECT_2 (3 << 5)
> +#define MAX9271_SPREAD_SPECT_3 (6 << 5)
> +#define MAX9271_SPREAD_SPECT_4 (7 << 5)
> +#define MAX9271_R02_RES BIT(4)
> +#define MAX9271_PCLK_AUTODETECT (3 << 2)
> +#define MAX9271_SERIAL_AUTODETECT (0x03)
> +/* Register 0x04 */
> +#define MAX9271_SEREN BIT(7)
> +#define MAX9271_CLINKEN BIT(6)
> +#define MAX9271_PRBSEN BIT(5)
> +#define MAX9271_SLEEP BIT(4)
> +#define MAX9271_INTTYPE_I2C (0 << 2)
> +#define MAX9271_INTTYPE_UART (1 << 2)
> +#define MAX9271_INTTYPE_NONE (2 << 2)
> +#define MAX9271_REVCCEN BIT(1)
> +#define MAX9271_FWDCCEN BIT(0)
> +/* Register 0x07 */
> +#define MAX9271_DBL BIT(7)
> +#define MAX9271_DRS BIT(6)
> +#define MAX9271_BWS BIT(5)
> +#define MAX9271_ES BIT(4)
> +#define MAX9271_HVEN BIT(2)
> +#define MAX9271_EDC_1BIT_PARITY (0 << 0)
> +#define MAX9271_EDC_6BIT_CRC (1 << 0)
> +#define MAX9271_EDC_6BIT_HAMMING (2 << 0)
> +/* Register 0x08 */
> +#define MAX9271_INVVS BIT(7)
> +#define MAX9271_INVHS BIT(6)
> +#define MAX9271_REV_LOGAIN BIT(3)
> +#define MAX9271_REV_HIVTH BIT(0)
> +/* Register 0x09 */
> +#define MAX9271_ID 0x09
> +/* Register 0x0d */
> +#define MAX9271_I2CLOCACK BIT(7)
> +#define MAX9271_I2CSLVSH_1046NS_469NS (3 << 5)
> +#define MAX9271_I2CSLVSH_938NS_352NS (2 << 5)
> +#define MAX9271_I2CSLVSH_469NS_234NS (1 << 5)
> +#define MAX9271_I2CSLVSH_352NS_117NS (0 << 5)
> +#define MAX9271_I2CMSTBT_837KBPS (7 << 2)
> +#define MAX9271_I2CMSTBT_533KBPS (6 << 2)
> +#define MAX9271_I2CMSTBT_339KBPS (5 << 2)
> +#define MAX9271_I2CMSTBT_173KBPS (4 << 2)
> +#define MAX9271_I2CMSTBT_105KBPS (3 << 2)
> +#define MAX9271_I2CMSTBT_84KBPS (2 << 2)
> +#define MAX9271_I2CMSTBT_28KBPS (1 << 2)
> +#define MAX9271_I2CMSTBT_8KBPS (0 << 2)
> +#define MAX9271_I2CSLVTO_NONE (3 << 0)
> +#define MAX9271_I2CSLVTO_1024US (2 << 0)
> +#define MAX9271_I2CSLVTO_256US (1 << 0)
> +#define MAX9271_I2CSLVTO_64US (0 << 0)
> +/* Register 0x0f */
> +#define MAX9271_GPIO5OUT BIT(5)
> +#define MAX9271_GPIO4OUT BIT(4)
> +#define MAX9271_GPIO3OUT BIT(3)
> +#define MAX9271_GPIO2OUT BIT(2)
> +#define MAX9271_GPIO1OUT BIT(1)
> +#define MAX9271_GPO BIT(0)
> +/* Register 0x15 */
> +#define MAX9271_PCLKDET BIT(0)
> +
> +/**
> + * struct max9271_device - max9271 device
> + * @client: The i2c client for the max9271 instance
> + */
> +struct max9271_device {
> + struct i2c_client *client;
> +};
> +
> +/**
> + * max9271_set_serial_link() - Enable/disable serial link
> + * @dev: The max9271 device
> + * @enable: Serial link enable/disable flag
> + *
> + * Return 0 on success or a negative error code on failure
> + */
> +int max9271_set_serial_link(struct max9271_device *dev, bool enable);
> +
> +/**
> + * max9271_configure_i2c() - Configure I2C bus parameters
> + * @dev: The max9271 device
> + * @i2c_config: The I2C bus configuration bit mask
> + *
> + * Configure MAX9271 I2C interface. The bus configuration provided in the
> + * @i2c_config parameter shall be assembled using bit values defined by the
> + * MAX9271_I2C* macros.
> + *
> + * Return 0 on success or a negative error code on failure
> + */
> +int max9271_configure_i2c(struct max9271_device *dev, u8 i2c_config);
> +
> +/**
> + * max9271_set_high_threshold() - Enable or disable reverse channel high
> + * threshold
> + * @dev: The max9271 device
> + * @enable: High threshold enable/disable flag
> + *
> + * Return 0 on success or a negative error code on failure
> + */
> +int max9271_set_high_threshold(struct max9271_device *dev, bool enable);
> +
> +/**
> + * max9271_configure_gmsl_link() - Configure the GMSL link
> + * @dev: The max9271 device
> + *
> + * FIXME: the GMSL link configuration is currently hardcoded and performed
> + * by programming registers 0x04, 0x07 and 0x02.
> + *
> + * Return 0 on success or a negative error code on failure
> + */
> +int max9271_configure_gmsl_link(struct max9271_device *dev);
> +
> +/**
> + * max9271_set_gpios() - Set gpio lines to physical high value
> + * @dev: The max9271 device
> + * @gpio_mask: The mask of gpio lines to set to high value
> + *
> + * The @gpio_mask parameter shall be assembled using the MAX9271_GP[IO|O]*
> + * bit values.
> + *
> + * Return 0 on success or a negative error code on failure
> + */
> +int max9271_set_gpios(struct max9271_device *dev, u8 gpio_mask);
> +
> +/**
> + * max9271_clear_gpios() - Set gpio lines to physical low value
> + * @dev: The max9271 device
> + * @gpio_mask: The mask of gpio lines to set to low value
> + *
> + * The @gpio_mask parameter shall be assembled using the MAX9271_GP[IO|O]*
> + * bit values.
> + *
> + * Return 0 on success or a negative error code on failure
> + */
> +int max9271_clear_gpios(struct max9271_device *dev, u8 gpio_mask);
> +
> +/**
> + * max9271_enable_gpios() - Enable gpio lines
> + * @dev: The max9271 device
> + * @gpio_mask: The mask of gpio lines to enable
> + *
> + * The @gpio_mask parameter shall be assembled using the MAX9271_GPIO*
> + * bit values. GPO line is always enabled by default.
> + *
> + * Return 0 on success or a negative error code on failure
> + */
> +int max9271_enable_gpios(struct max9271_device *dev, u8 gpio_mask);
> +
> +/**
> + * max9271_disable_gpios() - Disable gpio lines
> + * @dev: The max9271 device
> + * @gpio_mask: The mask of gpio lines to disable
> + *
> + * The @gpio_mask parameter shall be assembled using the MAX9271_GPIO*
> + * bit values. GPO line is always enabled by default and cannot be disabled.
> + *
> + * Return 0 on success or a negative error code on failure
> + */
> +int max9271_disable_gpios(struct max9271_device *dev, u8 gpio_mask);
> +
> +/**
> + * max9271_verify_id() - Read and verify MAX9271 id
> + * @dev: The max9271 device
> + *
> + * Return 0 on success or a negative error code on failure
> + */
> +int max9271_verify_id(struct max9271_device *dev);
> +
> +/**
> + * max9271_set_address() - Program a new I2C address
> + * @dev: The max9271 device
> + * @addr: The new I2C address in 7-bit format
> + *
> + * This function only takes care of programming the new I2C address @addr to
> + * in the MAX9271 chip registers, it is responsiblity of the caller to set
> + * the i2c address client to the @addr value to be able to communicate with
> + * the MAX9271 chip using the I2C framework APIs after this function returns.
> + *
> + * Return 0 on success or a negative error code on failure
> + */
> +int max9271_set_address(struct max9271_device *dev, u8 addr);
> +
> +/**
> + * max9271_set_deserializer_address() - Program the remote deserializer address
> + * @dev: The max9271 device
> + * @addr: The deserializer I2C address in 7-bit format
> + *
> + * Return 0 on success or a negative error code on failure
> + */
> +int max9271_set_deserializer_address(struct max9271_device *dev, u8 addr);
> +
> +/**
> + * max9271_set_translation() - Program I2C address translation
> + * @dev: The max9271 device
> + * @source: The I2C source address
> + * @dest: The I2C destination address
> + *
> + * Program address translation from @source to @dest. This is required to
> + * communicate with local devices that do not support address reprogramming.
> + *
> + * TODO: The device supports translation of two address, this function currently
> + * supports a single one.
> + *
> + * Return 0 on success or a negative error code on failure
> + */
> +int max9271_set_translation(struct max9271_device *dev, u8 source, u8 dest);
> diff --git a/drivers/media/i2c/rdacm20.c b/drivers/media/i2c/rdacm20.c
> new file mode 100644
> index 000000000000..cda3e6372ea9
> --- /dev/null
> +++ b/drivers/media/i2c/rdacm20.c
> @@ -0,0 +1,667 @@
> +// SPDX-License-Identifier: GPL-2.0+
> +/*
> + * IMI RDACM20 GMSL Camera Driver
> + *
> + * Copyright (C) 2017-2020 Jacopo Mondi
> + * Copyright (C) 2017-2020 Kieran Bingham
> + * Copyright (C) 2017-2019 Laurent Pinchart
> + * Copyright (C) 2017-2019 Niklas Söderlund
> + * Copyright (C) 2016 Renesas Electronics Corporation
> + * Copyright (C) 2015 Cogent Embedded, Inc.
> + */
> +
> +/*
> + * The camera is made of an Omnivision OV10635 sensor connected to a Maxim
> + * MAX9271 GMSL serializer.
> + */
> +
> +#include <linux/delay.h>
> +#include <linux/fwnode.h>
> +#include <linux/init.h>
> +#include <linux/i2c.h>
> +#include <linux/module.h>
> +#include <linux/slab.h>
> +#include <linux/videodev2.h>
> +
> +#include <media/v4l2-async.h>
> +#include <media/v4l2-ctrls.h>
> +#include <media/v4l2-subdev.h>
> +
> +#include "max9271.h"
> +
> +#define OV10635_I2C_ADDRESS 0x30
> +
> +#define OV10635_SOFTWARE_RESET 0x0103
> +#define OV10635_PID 0x300a
> +#define OV10635_VER 0x300b
> +#define OV10635_SC_CMMN_SCCB_ID 0x300c
> +#define OV10635_SC_CMMN_SCCB_ID_SELECT BIT(0)
> +#define OV10635_VERSION 0xa635
> +
> +#define OV10635_WIDTH 1280
> +#define OV10635_HEIGHT 800
> +
> +/* VTS = PCLK / FPS / HTS / 2 (= 88MHz / 1572 / 30 / 2) */
> +#define OV10635_HTS 1572
> +/* FPS = 29,9998 */
> +#define OV10635_VTS 933
> +
> +/*
> + * As the drivers supports a single MEDIA_BUS_FMT_UYVY8_2X8 format we
> + * can harcode the pixel rate.
Is this somehow the property of RDACM20? Or could you use other
configurations here? (I'm not suggesting you should do now.)
> + *
> + * PCLK is fed through the system clock, programmed @88MHz.
> + * MEDIA_BUS_FMT_UYVY8_2X8 format = 2 samples per pixel.
> + *
> + * Pixelrate = PCLK / 2
> + * FPS = (OV10635_VTS * OV10635_HTS) / PixelRate
> + * = 29,9998
> + */
> +#define OV10635_PIXEL_RATE (44000000)
> +
> +static const struct ov10635_reg {
> + u16 reg;
> + u8 val;
> +} ov10635_regs_wizard[] = {
> + { 0x301b, 0xff }, { 0x301c, 0xff }, { 0x301a, 0xff }, { 0x3011, 0x42 },
> + { 0x6900, 0x0c }, { 0x6901, 0x19 }, { 0x3503, 0x10 }, { 0x3025, 0x03 },
> + { 0x3003, 0x16 }, { 0x3004, 0x30 }, { 0x3005, 0x40 }, { 0x3006, 0x91 },
> + { 0x3600, 0x74 }, { 0x3601, 0x2b }, { 0x3612, 0x00 }, { 0x3611, 0x67 },
> + { 0x3633, 0xca }, { 0x3602, 0xaf }, { 0x3603, 0x04 }, { 0x3630, 0x28 },
> + { 0x3631, 0x16 }, { 0x3714, 0x10 }, { 0x371d, 0x01 }, { 0x4300, 0x3a },
> + { 0x3007, 0x01 }, { 0x3024, 0x03 }, { 0x3020, 0x0a }, { 0x3702, 0x0d },
> + { 0x3703, 0x20 }, { 0x3704, 0x15 }, { 0x3709, 0xa8 }, { 0x370c, 0xc7 },
> + { 0x370d, 0x80 }, { 0x3712, 0x00 }, { 0x3713, 0x20 }, { 0x3715, 0x04 },
> + { 0x381d, 0x40 }, { 0x381c, 0x00 }, { 0x3822, 0x50 }, { 0x3824, 0x10 },
> + { 0x3815, 0x8c }, { 0x3804, 0x05 }, { 0x3805, 0x1f }, { 0x3800, 0x00 },
> + { 0x3801, 0x00 }, { 0x3806, 0x03 }, { 0x3807, 0x28 }, { 0x3802, 0x00 },
> + { 0x3803, 0x07 }, { 0x3808, 0x05 }, { 0x3809, 0x00 }, { 0x380a, 0x03 },
> + { 0x380b, 0x20 }, { 0x380c, OV10635_HTS >> 8 },
> + { 0x380d, OV10635_HTS & 0xff }, { 0x380e, OV10635_VTS >> 8 },
> + { 0x380f, OV10635_VTS & 0xff }, { 0x3813, 0x02 }, { 0x3811, 0x08 },
> + { 0x381f, 0x0c }, { 0x3819, 0x04 }, { 0x3804, 0x01 }, { 0x3805, 0x00 },
> + { 0x3828, 0x03 }, { 0x3829, 0x10 }, { 0x382a, 0x10 }, { 0x3621, 0x63 },
> + { 0x5005, 0x08 }, { 0x56d5, 0x00 }, { 0x56d6, 0x80 }, { 0x56d7, 0x00 },
> + { 0x56d8, 0x00 }, { 0x56d9, 0x00 }, { 0x56da, 0x80 }, { 0x56db, 0x00 },
> + { 0x56dc, 0x00 }, { 0x56e8, 0x00 }, { 0x56e9, 0x7f }, { 0x56ea, 0x00 },
> + { 0x56eb, 0x7f }, { 0x5100, 0x00 }, { 0x5101, 0x80 }, { 0x5102, 0x00 },
> + { 0x5103, 0x80 }, { 0x5104, 0x00 }, { 0x5105, 0x80 }, { 0x5106, 0x00 },
> + { 0x5107, 0x80 }, { 0x5108, 0x00 }, { 0x5109, 0x00 }, { 0x510a, 0x00 },
> + { 0x510b, 0x00 }, { 0x510c, 0x00 }, { 0x510d, 0x00 }, { 0x510e, 0x00 },
> + { 0x510f, 0x00 }, { 0x5110, 0x00 }, { 0x5111, 0x80 }, { 0x5112, 0x00 },
> + { 0x5113, 0x80 }, { 0x5114, 0x00 }, { 0x5115, 0x80 }, { 0x5116, 0x00 },
> + { 0x5117, 0x80 }, { 0x5118, 0x00 }, { 0x5119, 0x00 }, { 0x511a, 0x00 },
> + { 0x511b, 0x00 }, { 0x511c, 0x00 }, { 0x511d, 0x00 }, { 0x511e, 0x00 },
> + { 0x511f, 0x00 }, { 0x56d0, 0x00 }, { 0x5006, 0x04 }, { 0x5608, 0x05 },
> + { 0x52d7, 0x06 }, { 0x528d, 0x08 }, { 0x5293, 0x12 }, { 0x52d3, 0x12 },
> + { 0x5288, 0x06 }, { 0x5289, 0x20 }, { 0x52c8, 0x06 }, { 0x52c9, 0x20 },
> + { 0x52cd, 0x04 }, { 0x5381, 0x00 }, { 0x5382, 0xff }, { 0x5589, 0x76 },
> + { 0x558a, 0x47 }, { 0x558b, 0xef }, { 0x558c, 0xc9 }, { 0x558d, 0x49 },
> + { 0x558e, 0x30 }, { 0x558f, 0x67 }, { 0x5590, 0x3f }, { 0x5591, 0xf0 },
> + { 0x5592, 0x10 }, { 0x55a2, 0x6d }, { 0x55a3, 0x55 }, { 0x55a4, 0xc3 },
> + { 0x55a5, 0xb5 }, { 0x55a6, 0x43 }, { 0x55a7, 0x38 }, { 0x55a8, 0x5f },
> + { 0x55a9, 0x4b }, { 0x55aa, 0xf0 }, { 0x55ab, 0x10 }, { 0x5581, 0x52 },
> + { 0x5300, 0x01 }, { 0x5301, 0x00 }, { 0x5302, 0x00 }, { 0x5303, 0x0e },
> + { 0x5304, 0x00 }, { 0x5305, 0x0e }, { 0x5306, 0x00 }, { 0x5307, 0x36 },
> + { 0x5308, 0x00 }, { 0x5309, 0xd9 }, { 0x530a, 0x00 }, { 0x530b, 0x0f },
> + { 0x530c, 0x00 }, { 0x530d, 0x2c }, { 0x530e, 0x00 }, { 0x530f, 0x59 },
> + { 0x5310, 0x00 }, { 0x5311, 0x7b }, { 0x5312, 0x00 }, { 0x5313, 0x22 },
> + { 0x5314, 0x00 }, { 0x5315, 0xd5 }, { 0x5316, 0x00 }, { 0x5317, 0x13 },
> + { 0x5318, 0x00 }, { 0x5319, 0x18 }, { 0x531a, 0x00 }, { 0x531b, 0x26 },
> + { 0x531c, 0x00 }, { 0x531d, 0xdc }, { 0x531e, 0x00 }, { 0x531f, 0x02 },
> + { 0x5320, 0x00 }, { 0x5321, 0x24 }, { 0x5322, 0x00 }, { 0x5323, 0x56 },
> + { 0x5324, 0x00 }, { 0x5325, 0x85 }, { 0x5326, 0x00 }, { 0x5327, 0x20 },
> + { 0x5609, 0x01 }, { 0x560a, 0x40 }, { 0x560b, 0x01 }, { 0x560c, 0x40 },
> + { 0x560d, 0x00 }, { 0x560e, 0xfa }, { 0x560f, 0x00 }, { 0x5610, 0xfa },
> + { 0x5611, 0x02 }, { 0x5612, 0x80 }, { 0x5613, 0x02 }, { 0x5614, 0x80 },
> + { 0x5615, 0x01 }, { 0x5616, 0x2c }, { 0x5617, 0x01 }, { 0x5618, 0x2c },
> + { 0x563b, 0x01 }, { 0x563c, 0x01 }, { 0x563d, 0x01 }, { 0x563e, 0x01 },
> + { 0x563f, 0x03 }, { 0x5640, 0x03 }, { 0x5641, 0x03 }, { 0x5642, 0x05 },
> + { 0x5643, 0x09 }, { 0x5644, 0x05 }, { 0x5645, 0x05 }, { 0x5646, 0x05 },
> + { 0x5647, 0x05 }, { 0x5651, 0x00 }, { 0x5652, 0x80 }, { 0x521a, 0x01 },
> + { 0x521b, 0x03 }, { 0x521c, 0x06 }, { 0x521d, 0x0a }, { 0x521e, 0x0e },
> + { 0x521f, 0x12 }, { 0x5220, 0x16 }, { 0x5223, 0x02 }, { 0x5225, 0x04 },
> + { 0x5227, 0x08 }, { 0x5229, 0x0c }, { 0x522b, 0x12 }, { 0x522d, 0x18 },
> + { 0x522f, 0x1e }, { 0x5241, 0x04 }, { 0x5242, 0x01 }, { 0x5243, 0x03 },
> + { 0x5244, 0x06 }, { 0x5245, 0x0a }, { 0x5246, 0x0e }, { 0x5247, 0x12 },
> + { 0x5248, 0x16 }, { 0x524a, 0x03 }, { 0x524c, 0x04 }, { 0x524e, 0x08 },
> + { 0x5250, 0x0c }, { 0x5252, 0x12 }, { 0x5254, 0x18 }, { 0x5256, 0x1e },
> + /* fifo_line_length = 2*hts */
> + { 0x4606, (2 * OV10635_HTS) >> 8 }, { 0x4607, (2 * OV10635_HTS) & 0xff },
> + /* fifo_hsync_start = 2*(hts - xres) */
> + { 0x460a, (2 * (OV10635_HTS - OV10635_WIDTH)) >> 8 },
> + { 0x460b, (2 * (OV10635_HTS - OV10635_WIDTH)) & 0xff },
> + { 0x460c, 0x00 }, { 0x4620, 0x0e },
> + /* BT601: 0x08 is also acceptable as HS/VS mode */
> + { 0x4700, 0x04 }, { 0x4701, 0x00 }, { 0x4702, 0x01 }, { 0x4004, 0x04 },
> + { 0x4005, 0x18 }, { 0x4001, 0x06 }, { 0x4050, 0x22 }, { 0x4051, 0x24 },
> + { 0x4052, 0x02 }, { 0x4057, 0x9c }, { 0x405a, 0x00 }, { 0x4202, 0x02 },
> + { 0x3023, 0x10 }, { 0x0100, 0x01 }, { 0x0100, 0x01 }, { 0x6f10, 0x07 },
> + { 0x6f11, 0x82 }, { 0x6f12, 0x04 }, { 0x6f13, 0x00 }, { 0xd000, 0x19 },
> + { 0xd001, 0xa0 }, { 0xd002, 0x00 }, { 0xd003, 0x01 }, { 0xd004, 0xa9 },
> + { 0xd005, 0xad }, { 0xd006, 0x10 }, { 0xd007, 0x40 }, { 0xd008, 0x44 },
> + { 0xd009, 0x00 }, { 0xd00a, 0x68 }, { 0xd00b, 0x00 }, { 0xd00c, 0x15 },
> + { 0xd00d, 0x00 }, { 0xd00e, 0x00 }, { 0xd00f, 0x00 }, { 0xd040, 0x9c },
> + { 0xd041, 0x21 }, { 0xd042, 0xff }, { 0xd043, 0xf8 }, { 0xd044, 0xd4 },
> + { 0xd045, 0x01 }, { 0xd046, 0x48 }, { 0xd047, 0x00 }, { 0xd048, 0xd4 },
> + { 0xd049, 0x01 }, { 0xd04a, 0x50 }, { 0xd04b, 0x04 }, { 0xd04c, 0x18 },
> + { 0xd04d, 0x60 }, { 0xd04e, 0x00 }, { 0xd04f, 0x01 }, { 0xd050, 0xa8 },
> + { 0xd051, 0x63 }, { 0xd052, 0x02 }, { 0xd053, 0xa4 }, { 0xd054, 0x85 },
> + { 0xd055, 0x43 }, { 0xd056, 0x00 }, { 0xd057, 0x00 }, { 0xd058, 0x18 },
> + { 0xd059, 0x60 }, { 0xd05a, 0x00 }, { 0xd05b, 0x01 }, { 0xd05c, 0xa8 },
> + { 0xd05d, 0x63 }, { 0xd05e, 0x03 }, { 0xd05f, 0xf0 }, { 0xd060, 0x98 },
> + { 0xd061, 0xa3 }, { 0xd062, 0x00 }, { 0xd063, 0x00 }, { 0xd064, 0x8c },
> + { 0xd065, 0x6a }, { 0xd066, 0x00 }, { 0xd067, 0x6e }, { 0xd068, 0xe5 },
> + { 0xd069, 0x85 }, { 0xd06a, 0x18 }, { 0xd06b, 0x00 }, { 0xd06c, 0x10 },
> + { 0xd06d, 0x00 }, { 0xd06e, 0x00 }, { 0xd06f, 0x10 }, { 0xd070, 0x9c },
> + { 0xd071, 0x80 }, { 0xd072, 0x00 }, { 0xd073, 0x03 }, { 0xd074, 0x18 },
> + { 0xd075, 0x60 }, { 0xd076, 0x00 }, { 0xd077, 0x01 }, { 0xd078, 0xa8 },
> + { 0xd079, 0x63 }, { 0xd07a, 0x07 }, { 0xd07b, 0x80 }, { 0xd07c, 0x07 },
> + { 0xd07d, 0xff }, { 0xd07e, 0xf9 }, { 0xd07f, 0x03 }, { 0xd080, 0x8c },
> + { 0xd081, 0x63 }, { 0xd082, 0x00 }, { 0xd083, 0x00 }, { 0xd084, 0xa5 },
> + { 0xd085, 0x6b }, { 0xd086, 0x00 }, { 0xd087, 0xff }, { 0xd088, 0x18 },
> + { 0xd089, 0x80 }, { 0xd08a, 0x00 }, { 0xd08b, 0x01 }, { 0xd08c, 0xa8 },
> + { 0xd08d, 0x84 }, { 0xd08e, 0x01 }, { 0xd08f, 0x04 }, { 0xd090, 0xe1 },
> + { 0xd091, 0x6b }, { 0xd092, 0x58 }, { 0xd093, 0x00 }, { 0xd094, 0x94 },
> + { 0xd095, 0x6a }, { 0xd096, 0x00 }, { 0xd097, 0x70 }, { 0xd098, 0xe1 },
> + { 0xd099, 0x6b }, { 0xd09a, 0x20 }, { 0xd09b, 0x00 }, { 0xd09c, 0x95 },
> + { 0xd09d, 0x6b }, { 0xd09e, 0x00 }, { 0xd09f, 0x00 }, { 0xd0a0, 0xe4 },
> + { 0xd0a1, 0x8b }, { 0xd0a2, 0x18 }, { 0xd0a3, 0x00 }, { 0xd0a4, 0x0c },
> + { 0xd0a5, 0x00 }, { 0xd0a6, 0x00 }, { 0xd0a7, 0x23 }, { 0xd0a8, 0x15 },
> + { 0xd0a9, 0x00 }, { 0xd0aa, 0x00 }, { 0xd0ab, 0x00 }, { 0xd0ac, 0x18 },
> + { 0xd0ad, 0x60 }, { 0xd0ae, 0x80 }, { 0xd0af, 0x06 }, { 0xd0b0, 0xa8 },
> + { 0xd0b1, 0x83 }, { 0xd0b2, 0x40 }, { 0xd0b3, 0x08 }, { 0xd0b4, 0xa8 },
> + { 0xd0b5, 0xe3 }, { 0xd0b6, 0x38 }, { 0xd0b7, 0x2a }, { 0xd0b8, 0xa8 },
> + { 0xd0b9, 0xc3 }, { 0xd0ba, 0x40 }, { 0xd0bb, 0x09 }, { 0xd0bc, 0xa8 },
> + { 0xd0bd, 0xa3 }, { 0xd0be, 0x38 }, { 0xd0bf, 0x29 }, { 0xd0c0, 0x8c },
> + { 0xd0c1, 0x65 }, { 0xd0c2, 0x00 }, { 0xd0c3, 0x00 }, { 0xd0c4, 0xd8 },
> + { 0xd0c5, 0x04 }, { 0xd0c6, 0x18 }, { 0xd0c7, 0x00 }, { 0xd0c8, 0x8c },
> + { 0xd0c9, 0x67 }, { 0xd0ca, 0x00 }, { 0xd0cb, 0x00 }, { 0xd0cc, 0xd8 },
> + { 0xd0cd, 0x06 }, { 0xd0ce, 0x18 }, { 0xd0cf, 0x00 }, { 0xd0d0, 0x18 },
> + { 0xd0d1, 0x60 }, { 0xd0d2, 0x80 }, { 0xd0d3, 0x06 }, { 0xd0d4, 0xa8 },
> + { 0xd0d5, 0xe3 }, { 0xd0d6, 0x67 }, { 0xd0d7, 0x02 }, { 0xd0d8, 0xa9 },
> + { 0xd0d9, 0x03 }, { 0xd0da, 0x67 }, { 0xd0db, 0x03 }, { 0xd0dc, 0xa8 },
> + { 0xd0dd, 0xc3 }, { 0xd0de, 0x3d }, { 0xd0df, 0x05 }, { 0xd0e0, 0x8c },
> + { 0xd0e1, 0x66 }, { 0xd0e2, 0x00 }, { 0xd0e3, 0x00 }, { 0xd0e4, 0xb8 },
> + { 0xd0e5, 0x63 }, { 0xd0e6, 0x00 }, { 0xd0e7, 0x18 }, { 0xd0e8, 0xb8 },
> + { 0xd0e9, 0x63 }, { 0xd0ea, 0x00 }, { 0xd0eb, 0x98 }, { 0xd0ec, 0xbc },
> + { 0xd0ed, 0x03 }, { 0xd0ee, 0x00 }, { 0xd0ef, 0x00 }, { 0xd0f0, 0x10 },
> + { 0xd0f1, 0x00 }, { 0xd0f2, 0x00 }, { 0xd0f3, 0x16 }, { 0xd0f4, 0xb8 },
> + { 0xd0f5, 0x83 }, { 0xd0f6, 0x00 }, { 0xd0f7, 0x19 }, { 0xd0f8, 0x8c },
> + { 0xd0f9, 0x67 }, { 0xd0fa, 0x00 }, { 0xd0fb, 0x00 }, { 0xd0fc, 0xb8 },
> + { 0xd0fd, 0xa4 }, { 0xd0fe, 0x00 }, { 0xd0ff, 0x98 }, { 0xd100, 0xb8 },
> + { 0xd101, 0x83 }, { 0xd102, 0x00 }, { 0xd103, 0x08 }, { 0xd104, 0x8c },
> + { 0xd105, 0x68 }, { 0xd106, 0x00 }, { 0xd107, 0x00 }, { 0xd108, 0xe0 },
> + { 0xd109, 0x63 }, { 0xd10a, 0x20 }, { 0xd10b, 0x04 }, { 0xd10c, 0xe0 },
> + { 0xd10d, 0x65 }, { 0xd10e, 0x18 }, { 0xd10f, 0x00 }, { 0xd110, 0xa4 },
> + { 0xd111, 0x83 }, { 0xd112, 0xff }, { 0xd113, 0xff }, { 0xd114, 0xb8 },
> + { 0xd115, 0x64 }, { 0xd116, 0x00 }, { 0xd117, 0x48 }, { 0xd118, 0xd8 },
> + { 0xd119, 0x07 }, { 0xd11a, 0x18 }, { 0xd11b, 0x00 }, { 0xd11c, 0xd8 },
> + { 0xd11d, 0x08 }, { 0xd11e, 0x20 }, { 0xd11f, 0x00 }, { 0xd120, 0x9c },
> + { 0xd121, 0x60 }, { 0xd122, 0x00 }, { 0xd123, 0x00 }, { 0xd124, 0xd8 },
> + { 0xd125, 0x06 }, { 0xd126, 0x18 }, { 0xd127, 0x00 }, { 0xd128, 0x00 },
> + { 0xd129, 0x00 }, { 0xd12a, 0x00 }, { 0xd12b, 0x08 }, { 0xd12c, 0x15 },
> + { 0xd12d, 0x00 }, { 0xd12e, 0x00 }, { 0xd12f, 0x00 }, { 0xd130, 0x8c },
> + { 0xd131, 0x6a }, { 0xd132, 0x00 }, { 0xd133, 0x76 }, { 0xd134, 0xbc },
> + { 0xd135, 0x23 }, { 0xd136, 0x00 }, { 0xd137, 0x00 }, { 0xd138, 0x13 },
> + { 0xd139, 0xff }, { 0xd13a, 0xff }, { 0xd13b, 0xe6 }, { 0xd13c, 0x18 },
> + { 0xd13d, 0x60 }, { 0xd13e, 0x80 }, { 0xd13f, 0x06 }, { 0xd140, 0x03 },
> + { 0xd141, 0xff }, { 0xd142, 0xff }, { 0xd143, 0xdd }, { 0xd144, 0xa8 },
> + { 0xd145, 0x83 }, { 0xd146, 0x40 }, { 0xd147, 0x08 }, { 0xd148, 0x85 },
> + { 0xd149, 0x21 }, { 0xd14a, 0x00 }, { 0xd14b, 0x00 }, { 0xd14c, 0x85 },
> + { 0xd14d, 0x41 }, { 0xd14e, 0x00 }, { 0xd14f, 0x04 }, { 0xd150, 0x44 },
> + { 0xd151, 0x00 }, { 0xd152, 0x48 }, { 0xd153, 0x00 }, { 0xd154, 0x9c },
> + { 0xd155, 0x21 }, { 0xd156, 0x00 }, { 0xd157, 0x08 }, { 0x6f0e, 0x03 },
> + { 0x6f0f, 0x00 }, { 0x460e, 0x08 }, { 0x460f, 0x01 }, { 0x4610, 0x00 },
> + { 0x4611, 0x01 }, { 0x4612, 0x00 }, { 0x4613, 0x01 },
> + /* 8 bits */
> + { 0x4605, 0x08 },
> + /* Swap data bits order [9:0] -> [0:9] */
> + { 0x4709, 0x10 }, { 0x4608, 0x00 }, { 0x4609, 0x08 }, { 0x6804, 0x00 },
> + { 0x6805, 0x06 }, { 0x6806, 0x00 }, { 0x5120, 0x00 }, { 0x3510, 0x00 },
> + { 0x3504, 0x00 }, { 0x6800, 0x00 }, { 0x6f0d, 0x01 },
> + /* PCLK falling edge */
> + { 0x4708, 0x01 }, { 0x5000, 0xff }, { 0x5001, 0xbf }, { 0x5002, 0x7e },
> + { 0x503d, 0x00 }, { 0xc450, 0x01 }, { 0xc452, 0x04 }, { 0xc453, 0x00 },
> + { 0xc454, 0x00 }, { 0xc455, 0x01 }, { 0xc456, 0x01 }, { 0xc457, 0x00 },
> + { 0xc458, 0x00 }, { 0xc459, 0x00 }, { 0xc45b, 0x00 }, { 0xc45c, 0x01 },
> + { 0xc45d, 0x00 }, { 0xc45e, 0x00 }, { 0xc45f, 0x00 }, { 0xc460, 0x00 },
> + { 0xc461, 0x01 }, { 0xc462, 0x01 }, { 0xc464, 0x03 }, { 0xc465, 0x00 },
> + { 0xc466, 0x8a }, { 0xc467, 0x00 }, { 0xc468, 0x86 }, { 0xc469, 0x00 },
> + { 0xc46a, 0x40 }, { 0xc46b, 0x50 }, { 0xc46c, 0x30 }, { 0xc46d, 0x28 },
> + { 0xc46e, 0x60 }, { 0xc46f, 0x40 }, { 0xc47c, 0x01 }, { 0xc47d, 0x38 },
> + { 0xc47e, 0x00 }, { 0xc47f, 0x00 }, { 0xc480, 0x00 }, { 0xc481, 0xff },
> + { 0xc482, 0x00 }, { 0xc483, 0x40 }, { 0xc484, 0x00 }, { 0xc485, 0x18 },
> + { 0xc486, 0x00 }, { 0xc487, 0x18 },
> + { 0xc488, (OV10635_VTS - 8) * 16 >> 8},
> + { 0xc489, (OV10635_VTS - 8) * 16 & 0xff},
> + { 0xc48a, (OV10635_VTS - 8) * 16 >> 8},
> + { 0xc48b, (OV10635_VTS - 8) * 16 & 0xff}, { 0xc48c, 0x00 },
> + { 0xc48d, 0x04 }, { 0xc48e, 0x00 }, { 0xc48f, 0x04 }, { 0xc490, 0x03 },
> + { 0xc492, 0x20 }, { 0xc493, 0x08 }, { 0xc498, 0x02 }, { 0xc499, 0x00 },
> + { 0xc49a, 0x02 }, { 0xc49b, 0x00 }, { 0xc49c, 0x02 }, { 0xc49d, 0x00 },
> + { 0xc49e, 0x02 }, { 0xc49f, 0x60 }, { 0xc4a0, 0x03 }, { 0xc4a1, 0x00 },
> + { 0xc4a2, 0x04 }, { 0xc4a3, 0x00 }, { 0xc4a4, 0x00 }, { 0xc4a5, 0x10 },
> + { 0xc4a6, 0x00 }, { 0xc4a7, 0x40 }, { 0xc4a8, 0x00 }, { 0xc4a9, 0x80 },
> + { 0xc4aa, 0x0d }, { 0xc4ab, 0x00 }, { 0xc4ac, 0x0f }, { 0xc4ad, 0xc0 },
> + { 0xc4b4, 0x01 }, { 0xc4b5, 0x01 }, { 0xc4b6, 0x00 }, { 0xc4b7, 0x01 },
> + { 0xc4b8, 0x00 }, { 0xc4b9, 0x01 }, { 0xc4ba, 0x01 }, { 0xc4bb, 0x00 },
> + { 0xc4bc, 0x01 }, { 0xc4bd, 0x60 }, { 0xc4be, 0x02 }, { 0xc4bf, 0x33 },
> + { 0xc4c8, 0x03 }, { 0xc4c9, 0xd0 }, { 0xc4ca, 0x0e }, { 0xc4cb, 0x00 },
> + { 0xc4cc, 0x0e }, { 0xc4cd, 0x51 }, { 0xc4ce, 0x0e }, { 0xc4cf, 0x51 },
> + { 0xc4d0, 0x04 }, { 0xc4d1, 0x80 }, { 0xc4e0, 0x04 }, { 0xc4e1, 0x02 },
> + { 0xc4e2, 0x01 }, { 0xc4e4, 0x10 }, { 0xc4e5, 0x20 }, { 0xc4e6, 0x30 },
> + { 0xc4e7, 0x40 }, { 0xc4e8, 0x50 }, { 0xc4e9, 0x60 }, { 0xc4ea, 0x70 },
> + { 0xc4eb, 0x80 }, { 0xc4ec, 0x90 }, { 0xc4ed, 0xa0 }, { 0xc4ee, 0xb0 },
> + { 0xc4ef, 0xc0 }, { 0xc4f0, 0xd0 }, { 0xc4f1, 0xe0 }, { 0xc4f2, 0xf0 },
> + { 0xc4f3, 0x80 }, { 0xc4f4, 0x00 }, { 0xc4f5, 0x20 }, { 0xc4f6, 0x02 },
> + { 0xc4f7, 0x00 }, { 0xc4f8, 0x00 }, { 0xc4f9, 0x00 }, { 0xc4fa, 0x00 },
> + { 0xc4fb, 0x01 }, { 0xc4fc, 0x01 }, { 0xc4fd, 0x00 }, { 0xc4fe, 0x04 },
> + { 0xc4ff, 0x02 }, { 0xc500, 0x48 }, { 0xc501, 0x74 }, { 0xc502, 0x58 },
> + { 0xc503, 0x80 }, { 0xc504, 0x05 }, { 0xc505, 0x80 }, { 0xc506, 0x03 },
> + { 0xc507, 0x80 }, { 0xc508, 0x01 }, { 0xc509, 0xc0 }, { 0xc50a, 0x01 },
> + { 0xc50b, 0xa0 }, { 0xc50c, 0x01 }, { 0xc50d, 0x2c }, { 0xc50e, 0x01 },
> + { 0xc50f, 0x0a }, { 0xc510, 0x00 }, { 0xc511, 0x00 }, { 0xc512, 0xe5 },
> + { 0xc513, 0x14 }, { 0xc514, 0x04 }, { 0xc515, 0x00 }, { 0xc518, OV10635_VTS >> 8},
> + { 0xc519, OV10635_VTS & 0xff}, { 0xc51a, OV10635_HTS >> 8},
> + { 0xc51b, OV10635_HTS & 0xff}, { 0xc2e0, 0x00 }, { 0xc2e1, 0x51 },
> + { 0xc2e2, 0x00 }, { 0xc2e3, 0xd6 }, { 0xc2e4, 0x01 }, { 0xc2e5, 0x5e },
> + { 0xc2e9, 0x01 }, { 0xc2ea, 0x7a }, { 0xc2eb, 0x90 }, { 0xc2ed, 0x00 },
> + { 0xc2ee, 0x7a }, { 0xc2ef, 0x64 }, { 0xc308, 0x00 }, { 0xc309, 0x00 },
> + { 0xc30a, 0x00 }, { 0xc30c, 0x00 }, { 0xc30d, 0x01 }, { 0xc30e, 0x00 },
> + { 0xc30f, 0x00 }, { 0xc310, 0x01 }, { 0xc311, 0x60 }, { 0xc312, 0xff },
> + { 0xc313, 0x08 }, { 0xc314, 0x01 }, { 0xc315, 0x00 }, { 0xc316, 0xff },
> + { 0xc317, 0x0b }, { 0xc318, 0x00 }, { 0xc319, 0x0c }, { 0xc31a, 0x00 },
> + { 0xc31b, 0xe0 }, { 0xc31c, 0x00 }, { 0xc31d, 0x14 }, { 0xc31e, 0x00 },
> + { 0xc31f, 0xc5 }, { 0xc320, 0xff }, { 0xc321, 0x4b }, { 0xc322, 0xff },
> + { 0xc323, 0xf0 }, { 0xc324, 0xff }, { 0xc325, 0xe8 }, { 0xc326, 0x00 },
> + { 0xc327, 0x46 }, { 0xc328, 0xff }, { 0xc329, 0xd2 }, { 0xc32a, 0xff },
> + { 0xc32b, 0xe4 }, { 0xc32c, 0xff }, { 0xc32d, 0xbb }, { 0xc32e, 0x00 },
> + { 0xc32f, 0x61 }, { 0xc330, 0xff }, { 0xc331, 0xf9 }, { 0xc332, 0x00 },
> + { 0xc333, 0xd9 }, { 0xc334, 0x00 }, { 0xc335, 0x2e }, { 0xc336, 0x00 },
> + { 0xc337, 0xb1 }, { 0xc338, 0xff }, { 0xc339, 0x64 }, { 0xc33a, 0xff },
> + { 0xc33b, 0xeb }, { 0xc33c, 0xff }, { 0xc33d, 0xe8 }, { 0xc33e, 0x00 },
> + { 0xc33f, 0x48 }, { 0xc340, 0xff }, { 0xc341, 0xd0 }, { 0xc342, 0xff },
> + { 0xc343, 0xed }, { 0xc344, 0xff }, { 0xc345, 0xad }, { 0xc346, 0x00 },
> + { 0xc347, 0x66 }, { 0xc348, 0x01 }, { 0xc349, 0x00 }, { 0x6700, 0x04 },
> + { 0x6701, 0x7b }, { 0x6702, 0xfd }, { 0x6703, 0xf9 }, { 0x6704, 0x3d },
> + { 0x6705, 0x71 }, { 0x6706, 0x78 }, { 0x6708, 0x05 }, { 0x6f06, 0x6f },
> + { 0x6f07, 0x00 }, { 0x6f0a, 0x6f }, { 0x6f0b, 0x00 }, { 0x6f00, 0x03 },
> + { 0xc34c, 0x01 }, { 0xc34d, 0x00 }, { 0xc34e, 0x46 }, { 0xc34f, 0x55 },
> + { 0xc350, 0x00 }, { 0xc351, 0x40 }, { 0xc352, 0x00 }, { 0xc353, 0xff },
> + { 0xc354, 0x04 }, { 0xc355, 0x08 }, { 0xc356, 0x01 }, { 0xc357, 0xef },
> + { 0xc358, 0x30 }, { 0xc359, 0x01 }, { 0xc35a, 0x64 }, { 0xc35b, 0x46 },
> + { 0xc35c, 0x00 }, { 0x3042, 0xf0 }, { 0x3042, 0xf0 }, { 0x3042, 0xf0 },
> + { 0x3042, 0xf0 }, { 0x3042, 0xf0 }, { 0x3042, 0xf0 }, { 0x3042, 0xf0 },
> + { 0x3042, 0xf0 }, { 0x3042, 0xf0 }, { 0x3042, 0xf0 }, { 0x3042, 0xf0 },
> + { 0x3042, 0xf0 }, { 0x3042, 0xf0 }, { 0x3042, 0xf0 }, { 0x3042, 0xf0 },
> + { 0x3042, 0xf0 }, { 0x3042, 0xf0 }, { 0x3042, 0xf0 }, { 0x3042, 0xf0 },
> + { 0x3042, 0xf0 }, { 0x3042, 0xf0 }, { 0x3042, 0xf0 }, { 0x3042, 0xf0 },
> + { 0x3042, 0xf0 }, { 0x3042, 0xf0 }, { 0x3042, 0xf0 }, { 0xc261, 0x01 },
> + { 0x301b, 0xf0 }, { 0x301c, 0xf0 }, { 0x301a, 0xf0 }, { 0x6f00, 0xc3 },
> + { 0xc46a, 0x30 }, { 0xc46d, 0x20 }, { 0xc464, 0x84 }, { 0xc465, 0x00 },
> + { 0x6f00, 0x03 }, { 0x6f00, 0x43 }, { 0x381c, 0x00 }, { 0x381d, 0x40 },
> + { 0xc454, 0x01 }, { 0x6f00, 0xc3 }, { 0xc454, 0x00 }, { 0xc4b1, 0x02 },
> + { 0xc4b2, 0x01 }, { 0xc4b3, 0x03 }, { 0x6f00, 0x03 }, { 0x6f00, 0x43 },
> + /* enable FSIN (FRAMESYNC input) functionality */
> + { 0x3832, (0x0d + 2 * 0x20 + 0x15 + 38) >> 8 },
> + { 0x3833, (0x0d + 2 * 0x20 + 0x15 + 38) & 0xff },
> + { 0x3834, OV10635_VTS >> 8 }, { 0x3835, OV10635_VTS & 0xff },
> + { 0x302e, 0x01 },
Ergh.
> +};
> +
> +struct rdacm20_device {
> + struct device *dev;
> + struct max9271_device *serializer;
> + struct i2c_client *sensor;
> + struct v4l2_subdev sd;
> + struct media_pad pad;
> + struct v4l2_ctrl_handler ctrls;
> + u32 addrs[2];
> +};
> +
> +static inline struct rdacm20_device *sd_to_rdacm20(struct v4l2_subdev *sd)
> +{
> + return container_of(sd, struct rdacm20_device, sd);
> +}
> +
> +static inline struct rdacm20_device *i2c_to_rdacm20(struct i2c_client *client)
> +{
> + return sd_to_rdacm20(i2c_get_clientdata(client));
> +}
> +
> +static int ov10635_read16(struct rdacm20_device *dev, u16 reg)
> +{
> + u8 buf[2] = { reg >> 8, reg & 0xff };
> + int ret;
> +
> + ret = i2c_master_send(dev->sensor, buf, 2);
> + if (ret != 2) {
> + dev_dbg(dev->dev, "%s: register 0x%04x write failed (%d)\n",
> + __func__, reg, ret);
> + return ret;
> + }
> +
> + ret = i2c_master_recv(dev->sensor, buf, 2);
> + if (ret < 0) {
> + dev_dbg(dev->dev, "%s: register 0x%04x read failed (%d)\n",
> + __func__, reg, ret);
> + return ret;
> + }
> +
> + return (buf[0] << 8) | buf[1];
> +}
> +
> +static int __ov10635_write(struct rdacm20_device *dev, u16 reg, u8 val)
> +{
> + u8 buf[3] = { reg >> 8, reg & 0xff, val };
> + int ret;
> +
> + dev_dbg(dev->dev, "%s(0x%04x, 0x%02x)\n", __func__, reg, val);
> +
> + ret = i2c_master_send(dev->sensor, buf, 3);
> + return ret < 0 ? ret : 0;
> +}
> +
> +static int ov10635_write(struct rdacm20_device *dev, u16 reg, u8 val)
> +{
> + int ret;
> +
> + ret = __ov10635_write(dev, reg, val);
> + if (ret < 0)
> + dev_err(dev->dev, "%s: register 0x%04x write failed (%d)\n",
> + __func__, reg, ret);
> +
> + return ret;
> +}
> +
> +static int ov10635_set_regs(struct rdacm20_device *dev,
> + const struct ov10635_reg *regs,
> + unsigned int nr_regs)
> +{
> + unsigned int i;
> + int ret;
> +
> + for (i = 0; i < nr_regs; i++) {
> + ret = __ov10635_write(dev, regs[i].reg, regs[i].val);
> + if (ret) {
> + dev_err(dev->dev,
> + "%s: register %u (0x%04x) write failed (%d)\n",
> + __func__, i, regs[i].reg, ret);
> + return ret;
> + }
> + }
> +
> + return 0;
> +}
> +
> +static int rdacm20_s_stream(struct v4l2_subdev *sd, int enable)
> +{
> + struct rdacm20_device *dev = sd_to_rdacm20(sd);
> +
> + return max9271_set_serial_link(dev->serializer, enable);
> +}
> +
> +static int rdacm20_enum_mbus_code(struct v4l2_subdev *sd,
> + struct v4l2_subdev_pad_config *cfg,
> + struct v4l2_subdev_mbus_code_enum *code)
> +{
> + if (code->pad || code->index > 0)
> + return -EINVAL;
> +
> + code->code = MEDIA_BUS_FMT_UYVY8_2X8;
> +
> + return 0;
> +}
> +
> +static int rdacm20_get_fmt(struct v4l2_subdev *sd,
> + struct v4l2_subdev_pad_config *cfg,
> + struct v4l2_subdev_format *format)
> +{
> + struct v4l2_mbus_framefmt *mf = &format->format;
> +
> + if (format->pad)
> + return -EINVAL;
> +
> + mf->width = OV10635_WIDTH;
> + mf->height = OV10635_HEIGHT;
> + mf->code = MEDIA_BUS_FMT_UYVY8_2X8;
> + mf->colorspace = V4L2_COLORSPACE_RAW;
> + mf->field = V4L2_FIELD_NONE;
> + mf->ycbcr_enc = V4L2_YCBCR_ENC_601;
> + mf->quantization = V4L2_QUANTIZATION_FULL_RANGE;
> + mf->xfer_func = V4L2_XFER_FUNC_NONE;
> +
> + return 0;
> +}
> +
> +static struct v4l2_subdev_video_ops rdacm20_video_ops = {
> + .s_stream = rdacm20_s_stream,
> +};
> +
> +static const struct v4l2_subdev_pad_ops rdacm20_subdev_pad_ops = {
> + .enum_mbus_code = rdacm20_enum_mbus_code,
> + .get_fmt = rdacm20_get_fmt,
> + .set_fmt = rdacm20_get_fmt,
> +};
> +
> +static struct v4l2_subdev_ops rdacm20_subdev_ops = {
> + .video = &rdacm20_video_ops,
> + .pad = &rdacm20_subdev_pad_ops,
> +};
> +
> +static int rdacm20_initialize(struct rdacm20_device *dev)
> +{
> + unsigned int retry = 3;
> + int ret;
> +
> + /* Verify communication with the MAX9271: ping to wakeup. */
> + dev->serializer->client->addr = MAX9271_DEFAULT_ADDR;
> + i2c_smbus_read_byte(dev->serializer->client);
> +
> + /* Serial link disabled during config as it needs a valid pixel clock. */
> + ret = max9271_set_serial_link(dev->serializer, false);
> + if (ret)
> + return ret;
> +
> + /*
> + * Ensure that we have a good link configuration before attempting to
> + * identify the device.
> + */
> + max9271_configure_i2c(dev->serializer, MAX9271_I2CSLVSH_469NS_234NS |
> + MAX9271_I2CSLVTO_1024US |
> + MAX9271_I2CMSTBT_105KBPS);
> +
> + max9271_configure_gmsl_link(dev->serializer);
> +
> + ret = max9271_verify_id(dev->serializer);
> + if (ret < 0)
> + return ret;
> +
> + ret = max9271_set_address(dev->serializer, dev->addrs[0]);
> + if (ret < 0)
> + return ret;
> + dev->serializer->client->addr = dev->addrs[0];
> +
> + /*
> + * Reset the sensor by cycling the OV10635 reset signal connected to the
> + * MAX9271 GPIO1 and verify communication with the OV10635.
> + */
> + max9271_clear_gpios(dev->serializer, MAX9271_GPIO1OUT);
> + mdelay(10);
Why mdelay(), a busy loop and not msleep()??? Same below.
> + max9271_set_gpios(dev->serializer, MAX9271_GPIO1OUT);
> + mdelay(10);
> +
> +again:
> + ret = ov10635_read16(dev, OV10635_PID);
> + if (ret < 0) {
> + if (retry--)
> + goto again;
> +
> + dev_err(dev->dev, "OV10635 ID read failed (%d)\n",
> + ret);
> + return -ENXIO;
> + }
> +
> + if (ret != OV10635_VERSION) {
> + if (retry--)
> + goto again;
> +
> + dev_err(dev->dev, "OV10635 ID mismatch (0x%04x)\n",
> + ret);
> + return -ENXIO;
> + }
> +
> + /* Change the sensor I2C address. */
> + ret = ov10635_write(dev, OV10635_SC_CMMN_SCCB_ID,
> + (dev->addrs[1] << 1) |
> + OV10635_SC_CMMN_SCCB_ID_SELECT);
> + if (ret < 0) {
> + dev_err(dev->dev,
> + "OV10635 I2C address change failed (%d)\n", ret);
> + return ret;
> + }
> + dev->sensor->addr = dev->addrs[1];
> + usleep_range(3500, 5000);
> +
> + /* Program the 0V10635 initial configuration. */
> + ret = ov10635_set_regs(dev, ov10635_regs_wizard,
> + ARRAY_SIZE(ov10635_regs_wizard));
> + if (ret)
> + return ret;
> +
> + dev_info(dev->dev, "Identified MAX9271 + OV10635 device\n");
> +
> + return 0;
> +}
> +
> +static int rdacm20_probe(struct i2c_client *client)
> +{
> + struct rdacm20_device *dev;
> + struct fwnode_handle *ep;
> + int ret;
> +
> + dev = devm_kzalloc(&client->dev, sizeof(*dev), GFP_KERNEL);
> + if (!dev)
> + return -ENOMEM;
> + dev->dev = &client->dev;
> +
> + dev->serializer = devm_kzalloc(&client->dev, sizeof(*dev->serializer),
> + GFP_KERNEL);
> + if (!dev->serializer)
> + return -ENOMEM;
> +
> + dev->serializer->client = client;
> +
> + ret = of_property_read_u32_array(client->dev.of_node, "reg",
> + dev->addrs, 2);
> + if (ret < 0) {
> + dev_err(dev->dev, "Invalid DT reg property: %d\n", ret);
> + return -EINVAL;
> + }
> +
> + /* Create the dummy I2C client for the sensor. */
> + dev->sensor = i2c_new_dummy_device(client->adapter,
> + OV10635_I2C_ADDRESS);
> + if (IS_ERR(dev->sensor)) {
> + ret = PTR_ERR(dev->sensor);
> + goto error;
> + }
> +
> + /* Initialize the hardware. */
> + ret = rdacm20_initialize(dev);
> + if (ret < 0)
> + goto error;
> +
> + /* Initialize and register the subdevice. */
> + v4l2_i2c_subdev_init(&dev->sd, client, &rdacm20_subdev_ops);
> + dev->sd.flags |= V4L2_SUBDEV_FL_HAS_DEVNODE;
> +
> + v4l2_ctrl_handler_init(&dev->ctrls, 1);
> + v4l2_ctrl_new_std(&dev->ctrls, NULL, V4L2_CID_PIXEL_RATE,
> + OV10635_PIXEL_RATE, OV10635_PIXEL_RATE, 1,
> + OV10635_PIXEL_RATE);
> + dev->sd.ctrl_handler = &dev->ctrls;
> +
> + ret = dev->ctrls.error;
> + if (ret)
> + goto error_free_ctrls;
> +
> + dev->pad.flags = MEDIA_PAD_FL_SOURCE;
> + dev->sd.entity.flags |= MEDIA_ENT_F_CAM_SENSOR;
> + ret = media_entity_pads_init(&dev->sd.entity, 1, &dev->pad);
> + if (ret < 0)
> + goto error_free_ctrls;
> +
> + ep = fwnode_graph_get_next_endpoint(dev_fwnode(&client->dev), NULL);
> + if (!ep) {
> + dev_err(&client->dev,
> + "Unable to get endpoint in node %pOF\n",
> + client->dev.of_node);
> + ret = -ENOENT;
> + goto error_free_ctrls;
> + }
> + dev->sd.fwnode = ep;
> +
> + ret = v4l2_async_register_subdev(&dev->sd);
> + if (ret)
> + goto error_put_node;
> +
> + return 0;
> +
> +error_put_node:
> + fwnode_handle_put(ep);
> +error_free_ctrls:
> + v4l2_ctrl_handler_free(&dev->ctrls);
> +error:
> + media_entity_cleanup(&dev->sd.entity);
> + if (dev->sensor)
> + i2c_unregister_device(dev->sensor);
> +
> + dev_err(&client->dev, "probe failed\n");
> +
> + return ret;
> +}
> +
> +static int rdacm20_remove(struct i2c_client *client)
> +{
> + struct rdacm20_device *dev = i2c_to_rdacm20(client);
> +
> + fwnode_handle_put(dev->sd.fwnode);
> + v4l2_async_unregister_subdev(&dev->sd);
> + v4l2_ctrl_handler_free(&dev->ctrls);
> + media_entity_cleanup(&dev->sd.entity);
> + i2c_unregister_device(dev->sensor);
> +
> + return 0;
> +}
> +
> +static void rdacm20_shutdown(struct i2c_client *client)
> +{
> + struct rdacm20_device *dev = i2c_to_rdacm20(client);
> +
> + /* make sure stream off during shutdown (reset/reboot) */
> + rdacm20_s_stream(&dev->sd, 0);
> +}
> +
> +static const struct of_device_id rdacm20_of_ids[] = {
> + { .compatible = "imi,rdacm20", },
> + { }
> +};
> +MODULE_DEVICE_TABLE(of, rdacm20_of_ids);
> +
> +static struct i2c_driver rdacm20_i2c_driver = {
> + .driver = {
> + .name = "rdacm20",
> + .of_match_table = rdacm20_of_ids,
> + },
> + .probe_new = rdacm20_probe,
> + .remove = rdacm20_remove,
> + .shutdown = rdacm20_shutdown,
> +};
> +
> +module_i2c_driver(rdacm20_i2c_driver);
> +
> +MODULE_DESCRIPTION("GMSL Camera driver for RDACM20");
> +MODULE_AUTHOR("Vladimir Barinov");
> +MODULE_LICENSE("GPL");
--
Regards,
Sakari Ailus
^ permalink raw reply
* [PATCH 12/12] gpu/drm: Ingenic: Support multiple panels/bridges
From: Paul Cercueil @ 2020-05-16 21:50 UTC (permalink / raw)
To: David Airlie, Daniel Vetter, Rob Herring, Greg Kroah-Hartman,
Rafael J . Wysocki
Cc: od, dri-devel, devicetree, linux-kernel, Paul Cercueil
In-Reply-To: <20200516215057.392609-1-paul@crapouillou.net>
Support multiple panels or bridges connected to the same DPI output of
the SoC. This setup can be found for instance on the GCW Zero, where the
same DPI output interfaces the internal 320x240 TFT panel, and the ITE
IT6610 HDMI chip.
Signed-off-by: Paul Cercueil <paul@crapouillou.net>
---
drivers/gpu/drm/ingenic/ingenic-drm.c | 74 ++++++++++++++++-----------
1 file changed, 43 insertions(+), 31 deletions(-)
diff --git a/drivers/gpu/drm/ingenic/ingenic-drm.c b/drivers/gpu/drm/ingenic/ingenic-drm.c
index ad1cec4f2a4d..091e3d3c9027 100644
--- a/drivers/gpu/drm/ingenic/ingenic-drm.c
+++ b/drivers/gpu/drm/ingenic/ingenic-drm.c
@@ -52,7 +52,6 @@ struct ingenic_drm {
struct drm_device drm;
struct drm_plane f0, f1, *ipu_plane;
struct drm_crtc crtc;
- struct drm_encoder encoder;
struct device *dev;
struct regmap *map;
@@ -106,12 +105,6 @@ static inline struct ingenic_drm *drm_crtc_get_priv(struct drm_crtc *crtc)
return container_of(crtc, struct ingenic_drm, crtc);
}
-static inline struct ingenic_drm *
-drm_encoder_get_priv(struct drm_encoder *encoder)
-{
- return container_of(encoder, struct ingenic_drm, encoder);
-}
-
static void ingenic_drm_crtc_atomic_enable(struct drm_crtc *crtc,
struct drm_crtc_state *state)
{
@@ -456,7 +449,7 @@ static void ingenic_drm_encoder_atomic_mode_set(struct drm_encoder *encoder,
struct drm_crtc_state *crtc_state,
struct drm_connector_state *conn_state)
{
- struct ingenic_drm *priv = drm_encoder_get_priv(encoder);
+ struct ingenic_drm *priv = drm_device_get_priv(encoder->dev);
struct drm_display_mode *mode = &crtc_state->adjusted_mode;
struct drm_connector *conn = conn_state->connector;
struct drm_display_info *info = &conn->display_info;
@@ -670,9 +663,11 @@ static int ingenic_drm_bind(struct device *dev)
struct clk *parent_clk;
struct drm_bridge *bridge;
struct drm_panel *panel;
+ struct drm_encoder *encoder;
struct drm_device *drm;
void __iomem *base;
long parent_rate;
+ unsigned int i, clone_mask = 0;
int ret, irq;
soc_info = of_device_get_match_data(dev);
@@ -744,17 +739,6 @@ static int ingenic_drm_bind(struct device *dev)
return PTR_ERR(priv->pix_clk);
}
- ret = drm_of_find_panel_or_bridge(dev->of_node, 0, 0, &panel, &bridge);
- if (ret) {
- if (ret != -EPROBE_DEFER)
- dev_err(dev, "Failed to get panel handle");
- return ret;
- }
-
- if (panel)
- bridge = devm_drm_panel_bridge_add_typed(dev, panel,
- DRM_MODE_CONNECTOR_DPI);
-
priv->dma_hwdesc[0] = dma_alloc_coherent(dev,
sizeof(*priv->dma_hwdesc[0]),
&priv->dma_hwdesc_phys[0],
@@ -821,22 +805,50 @@ static int ingenic_drm_bind(struct device *dev)
}
}
- priv->encoder.possible_crtcs = 1;
+ for (i = 0; ; i++) {
+ ret = drm_of_find_panel_or_bridge(dev->of_node, 0, i,
+ &panel, &bridge);
+ if (ret) {
+ if (ret == -ENODEV)
+ break; /* we're done */
+ if (ret != -EPROBE_DEFER)
+ dev_err(dev, "Failed to get bridge handle\n");
+ return ret;
+ }
- drm_encoder_helper_add(&priv->encoder,
- &ingenic_drm_encoder_helper_funcs);
+ if (panel)
+ bridge = devm_drm_panel_bridge_add_typed(dev, panel,
+ DRM_MODE_CONNECTOR_DPI);
- ret = drm_simple_encoder_init(drm, &priv->encoder,
- DRM_MODE_ENCODER_DPI);
- if (ret) {
- dev_err(dev, "Failed to init encoder: %i", ret);
- return ret;
+ encoder = devm_kzalloc(dev, sizeof(*encoder), GFP_KERNEL);
+ if (!encoder)
+ return -ENOMEM;
+
+ encoder->possible_crtcs = 1;
+
+ drm_encoder_helper_add(encoder,
+ &ingenic_drm_encoder_helper_funcs);
+
+ ret = drm_simple_encoder_init(drm, encoder,
+ DRM_MODE_ENCODER_DPI);
+ if (ret) {
+ dev_err(dev, "Failed to init encoder: %d\n", ret);
+ return ret;
+ }
+
+ ret = drm_bridge_attach(encoder, bridge, NULL, 0);
+ if (ret) {
+ dev_err(dev, "Unable to attach bridge");
+ return ret;
+ }
}
- ret = drm_bridge_attach(&priv->encoder, bridge, NULL, 0);
- if (ret) {
- dev_err(dev, "Unable to attach bridge");
- return ret;
+ drm_for_each_encoder(encoder, drm) {
+ clone_mask |= BIT(drm_encoder_index(encoder));
+ }
+
+ drm_for_each_encoder(encoder, drm) {
+ encoder->possible_clones = clone_mask;
}
ret = drm_irq_install(drm, irq);
--
2.26.2
^ permalink raw reply related
* [PATCH 11/12] gpu/drm: Ingenic: Add support for the IPU
From: Paul Cercueil @ 2020-05-16 21:50 UTC (permalink / raw)
To: David Airlie, Daniel Vetter, Rob Herring, Greg Kroah-Hartman,
Rafael J . Wysocki
Cc: od, dri-devel, devicetree, linux-kernel, Paul Cercueil
In-Reply-To: <20200516215057.392609-1-paul@crapouillou.net>
Add support for the Image Processing Unit (IPU) found in all Ingenic
SoCs.
The IPU can upscale and downscale a source frame of arbitrary size
ranging from 4x4 to 4096x4096 on newer SoCs, with bicubic filtering
on newer SoCs, bilinear filtering on older SoCs. Nearest-neighbour can
also be obtained with proper coefficients.
Starting from the JZ4725B, the IPU supports a mode where its output is
sent directly to the CRTC, without having to be written to RAM first.
This makes it possible to use the IPU as a DRM plane on the compatible
SoCs, and have it convert and scale anything the userspace asks for to
what's available for the display.
Regarding pixel formats, older SoCs support packed YUV 4:2:2 and various
planar YUV formats. Newer SoCs also support RGB.
The CRTC can toggle between the primary plane and the IPU, but cannot
display both at the same time. The IPU plane is then a primary plane
itself.
Signed-off-by: Paul Cercueil <paul@crapouillou.net>
---
drivers/gpu/drm/ingenic/Kconfig | 11 +
drivers/gpu/drm/ingenic/Makefile | 1 +
drivers/gpu/drm/ingenic/ingenic-drm.c | 67 +-
drivers/gpu/drm/ingenic/ingenic-drm.h | 9 +
drivers/gpu/drm/ingenic/ingenic-ipu.c | 861 ++++++++++++++++++++++++++
drivers/gpu/drm/ingenic/ingenic-ipu.h | 110 ++++
6 files changed, 1048 insertions(+), 11 deletions(-)
create mode 100644 drivers/gpu/drm/ingenic/ingenic-ipu.c
create mode 100644 drivers/gpu/drm/ingenic/ingenic-ipu.h
diff --git a/drivers/gpu/drm/ingenic/Kconfig b/drivers/gpu/drm/ingenic/Kconfig
index d82c3d37ec9c..80e10527d404 100644
--- a/drivers/gpu/drm/ingenic/Kconfig
+++ b/drivers/gpu/drm/ingenic/Kconfig
@@ -14,3 +14,14 @@ config DRM_INGENIC
Choose this option for DRM support for the Ingenic SoCs.
If M is selected the module will be called ingenic-drm.
+
+if DRM_INGENIC
+
+config DRM_INGENIC_IPU
+ tristate "IPU support for Ingenic SoCs"
+ help
+ Choose this option to enable support for the IPU found in Ingenic SoCs.
+
+ If M is selected the module will be called ingenic-ipu.
+
+endif
diff --git a/drivers/gpu/drm/ingenic/Makefile b/drivers/gpu/drm/ingenic/Makefile
index 11cac42ce0bb..df156194fbe5 100644
--- a/drivers/gpu/drm/ingenic/Makefile
+++ b/drivers/gpu/drm/ingenic/Makefile
@@ -1 +1,2 @@
obj-$(CONFIG_DRM_INGENIC) += ingenic-drm.o
+obj-$(CONFIG_DRM_INGENIC_IPU) += ingenic-ipu.o
diff --git a/drivers/gpu/drm/ingenic/ingenic-drm.c b/drivers/gpu/drm/ingenic/ingenic-drm.c
index b53db3ee001f..ad1cec4f2a4d 100644
--- a/drivers/gpu/drm/ingenic/ingenic-drm.c
+++ b/drivers/gpu/drm/ingenic/ingenic-drm.c
@@ -50,7 +50,7 @@ struct jz_soc_info {
struct ingenic_drm {
struct drm_device drm;
- struct drm_plane f0, f1;
+ struct drm_plane f0, f1, *ipu_plane;
struct drm_crtc crtc;
struct drm_encoder encoder;
@@ -186,13 +186,16 @@ static void ingenic_drm_crtc_update_timings(struct ingenic_drm *priv,
regmap_update_bits(priv->map, JZ_REG_LCD_CTRL,
JZ_LCD_CTRL_OFUP | JZ_LCD_CTRL_BURST_16,
JZ_LCD_CTRL_OFUP | JZ_LCD_CTRL_BURST_16);
+
+ regmap_write(priv->map, JZ_REG_LCD_IPUR, JZ_LCD_IPUR_IPUREN |
+ (ht * vpe / 3) << JZ_LCD_IPUR_IPUR_LSB);
}
static int ingenic_drm_crtc_atomic_check(struct drm_crtc *crtc,
struct drm_crtc_state *state)
{
struct ingenic_drm *priv = drm_crtc_get_priv(crtc);
- struct drm_plane_state *f1_state, *f0_state;
+ struct drm_plane_state *f1_state, *f0_state, *ipu_state;
long rate;
if (!drm_atomic_crtc_needs_modeset(state))
@@ -213,14 +216,41 @@ static int ingenic_drm_crtc_atomic_check(struct drm_crtc *crtc,
f0_state = drm_atomic_get_plane_state(state->state,
&priv->f0);
+ if (priv->ipu_plane)
+ ipu_state = drm_atomic_get_plane_state(state->state,
+ priv->ipu_plane);
+
+ /* IPU and F1 planes cannot be enabled at the same time. */
+ if (priv->ipu_plane && f1_state->fb && ipu_state->fb) {
+ dev_dbg(priv->dev, "Cannot enable both F1 and IPU\n");
+ return -EINVAL;
+ }
+
/* If all the planes are disabled, we won't get a VBLANK IRQ */
- if (!f1_state->fb && !f0_state->fb)
+ if (!f1_state->fb && !f0_state->fb &&
+ !(priv->ipu_plane && ipu_state->fb))
state->no_vblank = true;
}
return 0;
}
+static void ingenic_drm_crtc_atomic_begin(struct drm_crtc *crtc,
+ struct drm_crtc_state *oldstate)
+{
+ struct ingenic_drm *priv = drm_crtc_get_priv(crtc);
+ u32 ctrl = 0;
+
+ if (priv->soc_info->has_osd &&
+ drm_atomic_crtc_needs_modeset(crtc->state)) {
+ if (priv->ipu_plane && priv->ipu_plane->state->fb)
+ ctrl |= JZ_LCD_OSDCTRL_IPU;
+
+ regmap_update_bits(priv->map, JZ_REG_LCD_OSDCTRL,
+ JZ_LCD_OSDCTRL_IPU, ctrl);
+ }
+}
+
static void ingenic_drm_crtc_atomic_flush(struct drm_crtc *crtc,
struct drm_crtc_state *oldstate)
{
@@ -306,10 +336,9 @@ static void ingenic_drm_plane_enable(struct ingenic_drm *priv,
}
}
-static void ingenic_drm_plane_atomic_disable(struct drm_plane *plane,
- struct drm_plane_state *old_state)
+void ingenic_drm_plane_disable(struct device *dev, struct drm_plane *plane)
{
- struct ingenic_drm *priv = drm_device_get_priv(plane->dev);
+ struct ingenic_drm *priv = dev_get_drvdata(dev);
unsigned int en_bit;
if (priv->soc_info->has_osd) {
@@ -321,10 +350,20 @@ static void ingenic_drm_plane_atomic_disable(struct drm_plane *plane,
regmap_update_bits(priv->map, JZ_REG_LCD_OSDC, en_bit, 0);
}
}
+EXPORT_SYMBOL_GPL(ingenic_drm_plane_disable);
+
+static void ingenic_drm_plane_atomic_disable(struct drm_plane *plane,
+ struct drm_plane_state *old_state)
+{
+ struct ingenic_drm *priv = drm_device_get_priv(plane->dev);
+
+ ingenic_drm_plane_disable(priv->dev, plane);
+}
-static void ingenic_drm_plane_config(struct ingenic_drm *priv,
- struct drm_plane *plane, u32 fourcc)
+void ingenic_drm_plane_config(struct device *dev,
+ struct drm_plane *plane, u32 fourcc)
{
+ struct ingenic_drm *priv = dev_get_drvdata(dev);
struct drm_plane_state *state = plane->state;
unsigned int xy_reg, size_reg;
unsigned int ctrl = 0;
@@ -381,6 +420,7 @@ static void ingenic_drm_plane_config(struct ingenic_drm *priv,
state->crtc_h << JZ_LCD_SIZE01_HEIGHT_LSB);
}
}
+EXPORT_SYMBOL_GPL(ingenic_drm_plane_config);
static void ingenic_drm_plane_atomic_update(struct drm_plane *plane,
struct drm_plane_state *oldstate)
@@ -407,7 +447,7 @@ static void ingenic_drm_plane_atomic_update(struct drm_plane *plane,
priv->dma_hwdesc[hwdesc_idx]->cmd |= JZ_LCD_CMD_EOF_IRQ;
if (drm_atomic_crtc_needs_modeset(state->crtc->state))
- ingenic_drm_plane_config(priv, plane,
+ ingenic_drm_plane_config(priv->dev, plane,
state->fb->format->format);
}
}
@@ -588,6 +628,7 @@ static const struct drm_plane_helper_funcs ingenic_drm_plane_helper_funcs = {
static const struct drm_crtc_helper_funcs ingenic_drm_crtc_helper_funcs = {
.atomic_enable = ingenic_drm_crtc_atomic_enable,
.atomic_disable = ingenic_drm_crtc_atomic_disable,
+ .atomic_begin = ingenic_drm_crtc_atomic_begin,
.atomic_flush = ingenic_drm_crtc_atomic_flush,
.atomic_check = ingenic_drm_crtc_atomic_check,
};
@@ -738,15 +779,19 @@ static int ingenic_drm_bind(struct device *dev)
if (ret)
return ret;
+ if (soc_info->has_osd)
+ priv->ipu_plane = drm_plane_from_index(drm, 0);
+
drm_plane_helper_add(&priv->f1, &ingenic_drm_plane_helper_funcs);
ret = drm_universal_plane_init(drm, &priv->f1, 1,
&ingenic_drm_primary_plane_funcs,
ingenic_drm_primary_formats,
ARRAY_SIZE(ingenic_drm_primary_formats),
- NULL, DRM_PLANE_TYPE_PRIMARY, NULL);
+ NULL, DRM_PLANE_TYPE_PRIMARY,
+ NULL);
if (ret) {
- dev_err(dev, "Failed to register primary plane: %i", ret);
+ dev_err(dev, "Failed to register plane: %i", ret);
return ret;
}
diff --git a/drivers/gpu/drm/ingenic/ingenic-drm.h b/drivers/gpu/drm/ingenic/ingenic-drm.h
index b9d0bb0c9310..377c08d25639 100644
--- a/drivers/gpu/drm/ingenic/ingenic-drm.h
+++ b/drivers/gpu/drm/ingenic/ingenic-drm.h
@@ -9,6 +9,7 @@
#define DRIVERS_GPU_DRM_INGENIC_INGENIC_DRM_H
#include <linux/bitops.h>
+#include <linux/types.h>
#define JZ_REG_LCD_CFG 0x00
#define JZ_REG_LCD_VSYNC 0x04
@@ -159,4 +160,12 @@
#define JZ_LCD_SIZE01_WIDTH_LSB 0
#define JZ_LCD_SIZE01_HEIGHT_LSB 16
+struct device;
+struct drm_plane;
+struct drm_plane_state;
+
+void ingenic_drm_plane_config(struct device *dev,
+ struct drm_plane *plane, u32 fourcc);
+void ingenic_drm_plane_disable(struct device *dev, struct drm_plane *plane);
+
#endif /* DRIVERS_GPU_DRM_INGENIC_INGENIC_DRM_H */
diff --git a/drivers/gpu/drm/ingenic/ingenic-ipu.c b/drivers/gpu/drm/ingenic/ingenic-ipu.c
new file mode 100644
index 000000000000..6a11ba620dab
--- /dev/null
+++ b/drivers/gpu/drm/ingenic/ingenic-ipu.c
@@ -0,0 +1,861 @@
+// SPDX-License-Identifier: GPL-2.0
+//
+// Ingenic JZ47xx IPU driver
+//
+// Copyright (C) 2020, Paul Cercueil <paul@crapouillou.net>
+
+#include "ingenic-drm.h"
+#include "ingenic-ipu.h"
+
+#include <linux/clk.h>
+#include <linux/component.h>
+#include <linux/gcd.h>
+#include <linux/interrupt.h>
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/of_device.h>
+#include <linux/regmap.h>
+#include <linux/time.h>
+
+#include <drm/drm_atomic.h>
+#include <drm/drm_atomic_helper.h>
+#include <drm/drm_drv.h>
+#include <drm/drm_fb_cma_helper.h>
+#include <drm/drm_fourcc.h>
+#include <drm/drm_gem_framebuffer_helper.h>
+#include <drm/drm_plane.h>
+#include <drm/drm_plane_helper.h>
+#include <drm/drm_property.h>
+#include <drm/drm_vblank.h>
+
+struct ingenic_ipu;
+
+struct soc_info {
+ const u32 *formats;
+ size_t num_formats;
+ bool has_bicubic;
+
+ void (*set_coefs)(struct ingenic_ipu *ipu, unsigned int reg,
+ unsigned int sharpness, bool downscale,
+ unsigned int weight, unsigned int offset);
+};
+
+struct ingenic_ipu {
+ struct drm_plane plane;
+ struct device *dev, *master;
+ struct regmap *map;
+ struct clk *clk;
+ const struct soc_info *soc_info;
+
+ unsigned int numW, numH, denomW, denomH;
+
+ struct drm_property *sharpness_prop;
+ unsigned int sharpness;
+};
+
+/* Signed 15.16 fixed-point math (for bicubic scaling coefficients) */
+#define I2F(i) ((s32)(i) * 65536)
+#define F2I(f) ((f) / 65536)
+#define FMUL(fa, fb) ((s32)(((s64)(fa) * (s64)(fb)) / 65536))
+#define SHARPNESS_INCR (I2F(-1) / 8)
+
+static inline struct ingenic_ipu *plane_to_ingenic_ipu(struct drm_plane *plane)
+{
+ return container_of(plane, struct ingenic_ipu, plane);
+}
+
+static inline int regmap_set_bits(struct regmap *map,
+ unsigned int reg, unsigned int mask)
+{
+ return regmap_update_bits(map, reg, mask, mask);
+}
+
+/*
+ * Apply conventional cubic convolution kernel. Both parameters
+ * and return value are 15.16 signed fixed-point.
+ *
+ * @f_a: Sharpness factor, typically in range [-4.0, -0.25].
+ * A larger magnitude increases perceived sharpness, but going past
+ * -2.0 might cause ringing artifacts to outweigh any improvement.
+ * Nice values on a 320x240 LCD are between -0.75 and -2.0.
+ *
+ * @f_x: Absolute distance in pixels from 'pixel 0' sample position
+ * along horizontal (or vertical) source axis. Range is [0, +2.0].
+ *
+ * returns: Weight of this pixel within 4-pixel sample group. Range is
+ * [-2.0, +2.0]. For moderate (i.e. > -3.0) sharpness factors,
+ * range is within [-1.0, +1.0].
+ */
+static inline s32 cubic_conv(s32 f_a, s32 f_x)
+{
+ const s32 f_1 = I2F(1);
+ const s32 f_2 = I2F(2);
+ const s32 f_3 = I2F(3);
+ const s32 f_4 = I2F(4);
+ const s32 f_x2 = FMUL(f_x, f_x);
+ const s32 f_x3 = FMUL(f_x, f_x2);
+
+ if (f_x <= f_1)
+ return FMUL((f_a + f_2), f_x3) - FMUL((f_a + f_3), f_x2) + f_1;
+ else if (f_x <= f_2)
+ return FMUL(f_a, (f_x3 - 5 * f_x2 + 8 * f_x - f_4));
+ else
+ return 0;
+}
+
+/*
+ * On entry, "weight" is a coefficient suitable for bilinear mode,
+ * which is converted to a set of four suitable for bicubic mode.
+ *
+ * "weight 512" means all of pixel 0;
+ * "weight 256" means half of pixel 0 and half of pixel 1;
+ * "weight 0" means all of pixel 1;
+ *
+ * "offset" is increment to next source pixel sample location.
+ */
+static void jz4760_set_coefs(struct ingenic_ipu *ipu, unsigned int reg,
+ unsigned int sharpness, bool downscale,
+ unsigned int weight, unsigned int offset)
+{
+ u32 val;
+ s32 w0, w1, w2, w3; /* Pixel weights at X (or Y) offsets -1,0,1,2 */
+
+ weight = clamp_val(weight, 0, 512);
+
+ if (sharpness < 2) {
+ /*
+ * When sharpness setting is 0, emulate nearest-neighbor.
+ * When sharpness setting is 1, emulate bilinear.
+ */
+
+ if (sharpness == 0)
+ weight = weight >= 256 ? 512 : 0;
+ w0 = 0;
+ w1 = weight;
+ w2 = 512 - weight;
+ w3 = 0;
+ } else {
+ const s32 f_a = SHARPNESS_INCR * sharpness;
+ const s32 f_h = I2F(1) / 2; /* Round up 0.5 */
+
+ /*
+ * Note that always rounding towards +infinity here is intended.
+ * The resulting coefficients match a round-to-nearest-int
+ * double floating-point implementation.
+ */
+
+ weight = 512 - weight;
+ w0 = F2I(f_h + 512 * cubic_conv(f_a, I2F(512 + weight) / 512));
+ w1 = F2I(f_h + 512 * cubic_conv(f_a, I2F(0 + weight) / 512));
+ w2 = F2I(f_h + 512 * cubic_conv(f_a, I2F(512 - weight) / 512));
+ w3 = F2I(f_h + 512 * cubic_conv(f_a, I2F(1024 - weight) / 512));
+ w0 = clamp_val(w0, -1024, 1023);
+ w1 = clamp_val(w1, -1024, 1023);
+ w2 = clamp_val(w2, -1024, 1023);
+ w3 = clamp_val(w3, -1024, 1023);
+ }
+
+ val = ((w1 & JZ4760_IPU_RSZ_COEF_MASK) << JZ4760_IPU_RSZ_COEF31_LSB) |
+ ((w0 & JZ4760_IPU_RSZ_COEF_MASK) << JZ4760_IPU_RSZ_COEF20_LSB);
+ regmap_write(ipu->map, reg, val);
+
+ val = ((w3 & JZ4760_IPU_RSZ_COEF_MASK) << JZ4760_IPU_RSZ_COEF31_LSB) |
+ ((w2 & JZ4760_IPU_RSZ_COEF_MASK) << JZ4760_IPU_RSZ_COEF20_LSB) |
+ ((offset & JZ4760_IPU_RSZ_OFFSET_MASK) << JZ4760_IPU_RSZ_OFFSET_LSB);
+ regmap_write(ipu->map, reg, val);
+}
+
+static void jz4725b_set_coefs(struct ingenic_ipu *ipu, unsigned int reg,
+ unsigned int sharpness, bool downscale,
+ unsigned int weight, unsigned int offset)
+{
+ u32 val = JZ4725B_IPU_RSZ_LUT_OUT_EN;
+ unsigned int i;
+
+ weight = clamp_val(weight, 0, 512);
+
+ if (sharpness == 0)
+ weight = weight >= 256 ? 512 : 0;
+
+ val |= (weight & JZ4725B_IPU_RSZ_LUT_COEF_MASK) << JZ4725B_IPU_RSZ_LUT_COEF_LSB;
+ if (downscale || !!offset)
+ val |= JZ4725B_IPU_RSZ_LUT_IN_EN;
+
+ regmap_write(ipu->map, reg, val);
+
+ if (downscale) {
+ for (i = 1; i < offset; i++)
+ regmap_write(ipu->map, reg, JZ4725B_IPU_RSZ_LUT_IN_EN);
+ }
+}
+
+static void ingenic_ipu_set_downscale_coefs(struct ingenic_ipu *ipu,
+ unsigned int reg,
+ unsigned int num,
+ unsigned int denom)
+{
+ unsigned int i, offset, weight, weight_num = denom;
+
+ for (i = 0; i < num; i++) {
+ weight_num = num + (weight_num - num) % (num * 2);
+ weight = 512 - 512 * (weight_num - num) / (num * 2);
+ weight_num += denom * 2;
+ offset = (weight_num - num) / (num * 2);
+
+ ipu->soc_info->set_coefs(ipu, reg, ipu->sharpness,
+ true, weight, offset);
+ }
+}
+
+static void ingenic_ipu_set_integer_upscale_coefs(struct ingenic_ipu *ipu,
+ unsigned int reg,
+ unsigned int num)
+{
+ /*
+ * Force nearest-neighbor scaling and use simple math when upscaling
+ * by an integer ratio. It looks better, and fixes a few problem cases.
+ */
+ unsigned int i;
+
+ for (i = 0; i < num; i++)
+ ipu->soc_info->set_coefs(ipu, reg, 0, false, 512, i == num - 1);
+}
+
+static void ingenic_ipu_set_upscale_coefs(struct ingenic_ipu *ipu,
+ unsigned int reg,
+ unsigned int num,
+ unsigned int denom)
+{
+ unsigned int i, offset, weight, weight_num = 0;
+
+ for (i = 0; i < num; i++) {
+ weight = 512 - 512 * weight_num / num;
+ weight_num += denom;
+ offset = weight_num >= num;
+
+ if (offset)
+ weight_num -= num;
+
+ ipu->soc_info->set_coefs(ipu, reg, ipu->sharpness,
+ false, weight, offset);
+ }
+}
+
+static void ingenic_ipu_set_coefs(struct ingenic_ipu *ipu, unsigned int reg,
+ unsigned int num, unsigned int denom)
+{
+ /* Begin programming the LUT */
+ regmap_write(ipu->map, reg, -1);
+
+ if (denom > num)
+ ingenic_ipu_set_downscale_coefs(ipu, reg, num, denom);
+ else if (denom == 1)
+ ingenic_ipu_set_integer_upscale_coefs(ipu, reg, num);
+ else
+ ingenic_ipu_set_upscale_coefs(ipu, reg, num, denom);
+}
+
+static int reduce_fraction(unsigned int *num, unsigned int *denom)
+{
+ unsigned long d = gcd(*num, *denom);
+
+ /* The scaling table has only 31 entries */
+ if (*num > 31 * d)
+ return -EINVAL;
+
+ *num /= d;
+ *denom /= d;
+ return 0;
+}
+
+static inline bool scaling_required(struct drm_plane_state *state)
+{
+ return (state->src_w >> 16) != state->crtc_w &&
+ (state->src_h >> 16) != state->crtc_h;
+}
+
+static inline bool osd_changed(struct drm_plane_state *state,
+ struct drm_plane_state *oldstate)
+{
+ return state->crtc_x != oldstate->crtc_x ||
+ state->crtc_y != oldstate->crtc_y ||
+ state->crtc_w != oldstate->crtc_w ||
+ state->crtc_h != oldstate->crtc_h;
+}
+
+static void ingenic_ipu_plane_atomic_update(struct drm_plane *plane,
+ struct drm_plane_state *oldstate)
+{
+ struct ingenic_ipu *ipu = plane_to_ingenic_ipu(plane);
+ struct drm_plane_state *state = plane->state;
+ const struct drm_format_info *finfo;
+ u32 ctrl, stride = 0, coef_index = 0, format = 0;
+ bool needs_modeset, upscaling_w, upscaling_h;
+ dma_addr_t addr;
+
+ if (!state || !state->fb)
+ return;
+
+ finfo = drm_format_info(state->fb->format->format);
+
+ /* Reset all the registers if needed */
+ needs_modeset = drm_atomic_crtc_needs_modeset(state->crtc->state);
+ if (needs_modeset)
+ regmap_set_bits(ipu->map, JZ_REG_IPU_CTRL, JZ_IPU_CTRL_RST);
+
+ /* Enable the chip */
+ regmap_set_bits(ipu->map, JZ_REG_IPU_CTRL, JZ_IPU_CTRL_CHIP_EN);
+
+ /* Set addresses */
+ if (finfo->num_planes > 2) {
+ addr = drm_fb_cma_get_gem_addr(state->fb, state, 2);
+ regmap_write(ipu->map, JZ_REG_IPU_V_ADDR, addr);
+ }
+
+ if (finfo->num_planes > 1) {
+ addr = drm_fb_cma_get_gem_addr(state->fb, state, 1);
+ regmap_write(ipu->map, JZ_REG_IPU_U_ADDR, addr);
+ }
+
+ addr = drm_fb_cma_get_gem_addr(state->fb, state, 0);
+ regmap_write(ipu->map, JZ_REG_IPU_Y_ADDR, addr);
+
+ if (!needs_modeset)
+ return;
+
+ ingenic_drm_plane_config(ipu->master, plane, DRM_FORMAT_XRGB8888);
+
+ /* Set the input height/width/strides */
+ if (finfo->num_planes > 2)
+ stride = ((state->src_w >> 16) * finfo->cpp[2] / finfo->hsub)
+ << JZ_IPU_UV_STRIDE_V_LSB;
+
+ if (finfo->num_planes > 1)
+ stride |= ((state->src_w >> 16) * finfo->cpp[1] / finfo->hsub)
+ << JZ_IPU_UV_STRIDE_U_LSB;
+
+ regmap_write(ipu->map, JZ_REG_IPU_UV_STRIDE, stride);
+
+ stride = ((state->src_w >> 16) * finfo->cpp[0])
+ << JZ_IPU_Y_STRIDE_Y_LSB;
+ regmap_write(ipu->map, JZ_REG_IPU_Y_STRIDE, stride);
+
+ regmap_write(ipu->map, JZ_REG_IPU_IN_GS,
+ (stride << JZ_IPU_IN_GS_W_LSB) |
+ ((state->src_h >> 16) << JZ_IPU_IN_GS_H_LSB));
+
+ switch (finfo->format) {
+ case DRM_FORMAT_XRGB1555:
+ format = JZ_IPU_D_FMT_IN_FMT_RGB555 |
+ JZ_IPU_D_FMT_RGB_OUT_OFT_RGB;
+ break;
+ case DRM_FORMAT_XBGR1555:
+ format = JZ_IPU_D_FMT_IN_FMT_RGB555 |
+ JZ_IPU_D_FMT_RGB_OUT_OFT_BGR;
+ break;
+ case DRM_FORMAT_RGB565:
+ format = JZ_IPU_D_FMT_IN_FMT_RGB565 |
+ JZ_IPU_D_FMT_RGB_OUT_OFT_RGB;
+ break;
+ case DRM_FORMAT_BGR565:
+ format = JZ_IPU_D_FMT_IN_FMT_RGB565 |
+ JZ_IPU_D_FMT_RGB_OUT_OFT_BGR;
+ break;
+ case DRM_FORMAT_XRGB8888:
+ format = JZ_IPU_D_FMT_IN_FMT_RGB888 |
+ JZ_IPU_D_FMT_RGB_OUT_OFT_RGB;
+ break;
+ case DRM_FORMAT_XBGR8888:
+ format = JZ_IPU_D_FMT_IN_FMT_RGB888 |
+ JZ_IPU_D_FMT_RGB_OUT_OFT_BGR;
+ break;
+ case DRM_FORMAT_YUYV:
+ format = JZ_IPU_D_FMT_IN_FMT_YUV422 |
+ JZ_IPU_D_FMT_YUV_VY1UY0;
+ break;
+ case DRM_FORMAT_YVYU:
+ format = JZ_IPU_D_FMT_IN_FMT_YUV422 |
+ JZ_IPU_D_FMT_YUV_UY1VY0;
+ break;
+ case DRM_FORMAT_UYVY:
+ format = JZ_IPU_D_FMT_IN_FMT_YUV422 |
+ JZ_IPU_D_FMT_YUV_Y1VY0U;
+ break;
+ case DRM_FORMAT_VYUY:
+ format = JZ_IPU_D_FMT_IN_FMT_YUV422 |
+ JZ_IPU_D_FMT_YUV_Y1UY0V;
+ break;
+ case DRM_FORMAT_YUV411:
+ format = JZ_IPU_D_FMT_IN_FMT_YUV411;
+ break;
+ case DRM_FORMAT_YUV420:
+ format = JZ_IPU_D_FMT_IN_FMT_YUV420;
+ break;
+ case DRM_FORMAT_YUV422:
+ format = JZ_IPU_D_FMT_IN_FMT_YUV422;
+ break;
+ case DRM_FORMAT_YUV444:
+ format = JZ_IPU_D_FMT_IN_FMT_YUV444;
+ break;
+ default:
+ WARN_ONCE(1, "Unsupported format");
+ break;
+ }
+
+ /* Fix output to RGB888 */
+ format |= JZ_IPU_D_FMT_OUT_FMT_RGB888;
+
+ /* Set pixel format */
+ regmap_write(ipu->map, JZ_REG_IPU_D_FMT, format);
+
+ /* Set the output height/width/stride */
+ regmap_write(ipu->map, JZ_REG_IPU_OUT_GS,
+ ((state->crtc_w * 4) << JZ_IPU_OUT_GS_W_LSB)
+ | state->crtc_h << JZ_IPU_OUT_GS_H_LSB);
+ regmap_write(ipu->map, JZ_REG_IPU_OUT_STRIDE, state->crtc_w * 4);
+
+ ctrl = JZ_IPU_CTRL_LCDC_SEL | JZ_IPU_CTRL_FM_IRQ_EN;
+
+ if (finfo->num_planes == 1)
+ ctrl |= JZ_IPU_CTRL_SPKG_SEL;
+ if (finfo->is_yuv)
+ ctrl |= JZ_IPU_CTRL_CSC_EN;
+
+ regmap_update_bits(ipu->map, JZ_REG_IPU_CTRL,
+ JZ_IPU_CTRL_LCDC_SEL | JZ_IPU_CTRL_FM_IRQ_EN |
+ JZ_IPU_CTRL_SPKG_SEL | JZ_IPU_CTRL_CSC_EN, ctrl);
+
+ if (finfo->is_yuv) {
+ /*
+ * Offsets for Chroma/Luma.
+ * y = source Y - LUMA,
+ * u = source Cb - CHROMA,
+ * v = source Cr - CHROMA
+ */
+ regmap_write(ipu->map, JZ_REG_IPU_CSC_OFFSET,
+ 128 << JZ_IPU_CSC_OFFSET_CHROMA_LSB |
+ 0 << JZ_IPU_CSC_OFFSET_LUMA_LSB);
+
+ /*
+ * YUV422 to RGB conversion table.
+ * R = C0 / 0x400 * y + C1 / 0x400 * v
+ * G = C0 / 0x400 * y - C2 / 0x400 * u - C3 / 0x400 * v
+ * B = C0 / 0x400 * y + C4 / 0x400 * u
+ */
+ regmap_write(ipu->map, JZ_REG_IPU_CSC_C0_COEF, 0x4a8);
+ regmap_write(ipu->map, JZ_REG_IPU_CSC_C1_COEF, 0x662);
+ regmap_write(ipu->map, JZ_REG_IPU_CSC_C2_COEF, 0x191);
+ regmap_write(ipu->map, JZ_REG_IPU_CSC_C3_COEF, 0x341);
+ regmap_write(ipu->map, JZ_REG_IPU_CSC_C4_COEF, 0x811);
+ }
+
+ ctrl = 0;
+
+ /*
+ * Must set ZOOM_SEL before programming bicubic LUTs.
+ * If the IPU supports bicubic, we enable it unconditionally, since it
+ * can do anything bilinear can and more.
+ */
+ if (ipu->soc_info->has_bicubic)
+ ctrl |= JZ_IPU_CTRL_ZOOM_SEL;
+
+ upscaling_w = ipu->numW > ipu->denomW;
+ if (upscaling_w)
+ ctrl |= JZ_IPU_CTRL_HSCALE;
+
+ if (ipu->numW != 1 || ipu->denomW != 1) {
+ if (!ipu->soc_info->has_bicubic && !upscaling_w)
+ coef_index |= (ipu->denomW - 1) << 16;
+ else
+ coef_index |= (ipu->numW - 1) << 16;
+ ctrl |= JZ_IPU_CTRL_HRSZ_EN;
+ }
+
+ upscaling_h = ipu->numH > ipu->denomH;
+ if (upscaling_h)
+ ctrl |= JZ_IPU_CTRL_VSCALE;
+
+ if (ipu->numH != 1 || ipu->denomH != 1) {
+ if (!ipu->soc_info->has_bicubic && !upscaling_h)
+ coef_index |= ipu->denomH - 1;
+ else
+ coef_index |= ipu->numH - 1;
+ ctrl |= JZ_IPU_CTRL_VRSZ_EN;
+ }
+
+ regmap_update_bits(ipu->map, JZ_REG_IPU_CTRL, JZ_IPU_CTRL_ZOOM_SEL |
+ JZ_IPU_CTRL_HRSZ_EN | JZ_IPU_CTRL_VRSZ_EN |
+ JZ_IPU_CTRL_HSCALE | JZ_IPU_CTRL_VSCALE, ctrl);
+
+ /* Set the LUT index register */
+ regmap_write(ipu->map, JZ_REG_IPU_RSZ_COEF_INDEX, coef_index);
+
+ if (ipu->numW != 1 || ipu->denomW != 1)
+ ingenic_ipu_set_coefs(ipu, JZ_REG_IPU_HRSZ_COEF_LUT,
+ ipu->numW, ipu->denomW);
+
+ if (ipu->numH != 1 || ipu->denomH != 1)
+ ingenic_ipu_set_coefs(ipu, JZ_REG_IPU_VRSZ_COEF_LUT,
+ ipu->numH, ipu->denomH);
+
+ /* Clear STATUS register */
+ regmap_write(ipu->map, JZ_REG_IPU_STATUS, 0);
+
+ /* Start IPU */
+ regmap_set_bits(ipu->map, JZ_REG_IPU_CTRL, JZ_IPU_CTRL_RUN);
+
+ dev_dbg(ipu->dev, "Scaling %ux%u to %ux%u (%u:%u horiz, %u:%u vert)\n",
+ state->src_w >> 16, state->src_h >> 16,
+ state->crtc_w, state->crtc_h,
+ ipu->numW, ipu->denomW, ipu->numH, ipu->denomH);
+}
+
+static int ingenic_ipu_plane_atomic_check(struct drm_plane *plane,
+ struct drm_plane_state *state)
+{
+ unsigned int numW, denomW, numH, denomH, xres, yres;
+ struct ingenic_ipu *ipu = plane_to_ingenic_ipu(plane);
+ struct drm_crtc *crtc = state->crtc ?: plane->state->crtc;
+ struct drm_crtc_state *crtc_state;
+
+ if (!crtc)
+ return 0;
+
+ crtc_state = drm_atomic_get_existing_crtc_state(state->state, crtc);
+ if (WARN_ON(!crtc_state))
+ return -EINVAL;
+
+ /* Request a full modeset if we are enabling or disabling the IPU. */
+ if (!plane->state->crtc ^ !state->crtc)
+ crtc_state->mode_changed = true;
+
+ if (!state->crtc ||
+ !crtc_state->mode.hdisplay || !crtc_state->mode.vdisplay)
+ return 0;
+
+ /* Plane must be fully visible */
+ if (state->crtc_x < 0 || state->crtc_y < 0 ||
+ state->crtc_x + state->crtc_w > crtc_state->mode.hdisplay ||
+ state->crtc_y + state->crtc_h > crtc_state->mode.vdisplay)
+ return -EINVAL;
+
+ /* Minimum size is 4x4 */
+ if ((state->src_w >> 16) < 4 || (state->src_h >> 16) < 4)
+ return -EINVAL;
+
+ /* Input and output lines must have an even number of pixels. */
+ if (((state->src_w >> 16) & 1) || (state->crtc_w & 1))
+ return -EINVAL;
+
+ if (!scaling_required(state)) {
+ if (plane->state && osd_changed(state, plane->state))
+ crtc_state->mode_changed = true;
+
+ ipu->numW = ipu->numH = ipu->denomW = ipu->denomH = 1;
+ return 0;
+ }
+
+ crtc_state->mode_changed = true;
+
+ xres = state->src_w >> 16;
+ yres = state->src_h >> 16;
+
+ numW = state->crtc_w;
+ numH = state->crtc_h;
+ denomW = xres;
+ denomH = yres;
+
+ /* Adjust the coefficients until we find a valid configuration */
+ while (numW <= crtc_state->mode.hdisplay &&
+ reduce_fraction(&numW, &denomW) < 0)
+ numW++;
+ if (numW > crtc_state->mode.hdisplay)
+ return -EINVAL;
+
+ while (numH <= crtc_state->mode.vdisplay &&
+ reduce_fraction(&numH, &denomH) < 0)
+ numH++;
+ if (numH > crtc_state->mode.vdisplay)
+ return -EINVAL;
+
+ ipu->numW = numW;
+ ipu->numH = numH;
+ ipu->denomW = denomW;
+ ipu->denomH = denomH;
+
+ return 0;
+}
+
+static void ingenic_ipu_plane_atomic_disable(struct drm_plane *plane,
+ struct drm_plane_state *old_state)
+{
+ struct ingenic_ipu *ipu = plane_to_ingenic_ipu(plane);
+
+ regmap_set_bits(ipu->map, JZ_REG_IPU_CTRL, JZ_IPU_CTRL_STOP);
+ regmap_update_bits(ipu->map, JZ_REG_IPU_CTRL, JZ_IPU_CTRL_CHIP_EN, 0);
+
+ ingenic_drm_plane_disable(ipu->master, plane);
+}
+
+static const struct drm_plane_helper_funcs ingenic_ipu_plane_helper_funcs = {
+ .atomic_update = ingenic_ipu_plane_atomic_update,
+ .atomic_check = ingenic_ipu_plane_atomic_check,
+ .atomic_disable = ingenic_ipu_plane_atomic_disable,
+ .prepare_fb = drm_gem_fb_prepare_fb,
+};
+
+static int
+ingenic_ipu_plane_atomic_get_property(struct drm_plane *plane,
+ const struct drm_plane_state *state,
+ struct drm_property *property, u64 *val)
+{
+ struct ingenic_ipu *ipu = plane_to_ingenic_ipu(plane);
+
+ if (property != ipu->sharpness_prop)
+ return -EINVAL;
+
+ *val = ipu->sharpness;
+
+ return 0;
+}
+
+static int
+ingenic_ipu_plane_atomic_set_property(struct drm_plane *plane,
+ struct drm_plane_state *state,
+ struct drm_property *property, u64 val)
+{
+ struct ingenic_ipu *ipu = plane_to_ingenic_ipu(plane);
+ struct drm_crtc_state *crtc_state;
+
+ if (property != ipu->sharpness_prop)
+ return -EINVAL;
+
+ ipu->sharpness = val;
+
+ if (state->crtc) {
+ crtc_state = drm_atomic_get_existing_crtc_state(state->state,
+ state->crtc);
+ if (WARN_ON(!crtc_state))
+ return -EINVAL;
+
+ crtc_state->mode_changed = true;
+ }
+
+ return 0;
+}
+
+static const struct drm_plane_funcs ingenic_ipu_plane_funcs = {
+ .update_plane = drm_atomic_helper_update_plane,
+ .disable_plane = drm_atomic_helper_disable_plane,
+ .reset = drm_atomic_helper_plane_reset,
+ .destroy = drm_plane_cleanup,
+
+ .atomic_duplicate_state = drm_atomic_helper_plane_duplicate_state,
+ .atomic_destroy_state = drm_atomic_helper_plane_destroy_state,
+
+ .atomic_get_property = ingenic_ipu_plane_atomic_get_property,
+ .atomic_set_property = ingenic_ipu_plane_atomic_set_property,
+};
+
+static irqreturn_t ingenic_ipu_irq_handler(int irq, void *arg)
+{
+ struct drm_plane *plane = drm_plane_from_index(arg, 0);
+ struct ingenic_ipu *ipu = plane_to_ingenic_ipu(plane);
+ struct drm_crtc *crtc = drm_crtc_from_index(arg, 0);
+
+ regmap_write(ipu->map, JZ_REG_IPU_STATUS, 0);
+
+ drm_crtc_handle_vblank(crtc);
+
+ return IRQ_HANDLED;
+}
+
+static const struct regmap_config ingenic_ipu_regmap_config = {
+ .reg_bits = 32,
+ .val_bits = 32,
+ .reg_stride = 4,
+
+ .max_register = JZ_REG_IPU_OUT_PHY_T_ADDR,
+};
+
+static int ingenic_ipu_bind(struct device *dev, struct device *master, void *d)
+{
+ struct platform_device *pdev = to_platform_device(dev);
+ const struct soc_info *soc_info;
+ struct drm_device *drm = d;
+ struct drm_plane *plane;
+ struct ingenic_ipu *ipu;
+ void __iomem *base;
+ unsigned int sharpness_max;
+ int err, irq;
+
+ ipu = devm_kzalloc(dev, sizeof(*ipu), GFP_KERNEL);
+ if (!ipu)
+ return -ENOMEM;
+
+ soc_info = of_device_get_match_data(dev);
+ if (!soc_info) {
+ dev_err(dev, "Missing platform data\n");
+ return -EINVAL;
+ }
+
+ ipu->dev = dev;
+ ipu->master = master;
+ ipu->soc_info = soc_info;
+
+ base = devm_platform_ioremap_resource(pdev, 0);
+ if (IS_ERR(base)) {
+ dev_err(dev, "Failed to get memory resource\n");
+ return PTR_ERR(base);
+ }
+
+ ipu->map = devm_regmap_init_mmio(dev, base, &ingenic_ipu_regmap_config);
+ if (IS_ERR(ipu->map)) {
+ dev_err(dev, "Failed to create regmap\n");
+ return PTR_ERR(ipu->map);
+ }
+
+ irq = platform_get_irq(pdev, 0);
+ if (irq < 0)
+ return irq;
+
+ ipu->clk = devm_clk_get(dev, "ipu");
+ if (IS_ERR(ipu->clk)) {
+ dev_err(dev, "Failed to get pixel clock\n");
+ return PTR_ERR(ipu->clk);
+ }
+
+ err = devm_request_irq(dev, irq, ingenic_ipu_irq_handler, 0,
+ drm->driver->name, drm);
+ if (err) {
+ dev_err(dev, "Unable to request IRQ\n");
+ return err;
+ }
+
+ plane = &ipu->plane;
+ dev_set_drvdata(dev, plane);
+
+ drm_plane_helper_add(plane, &ingenic_ipu_plane_helper_funcs);
+
+ err = drm_universal_plane_init(drm, plane, 1, &ingenic_ipu_plane_funcs,
+ soc_info->formats, soc_info->num_formats,
+ NULL, DRM_PLANE_TYPE_PRIMARY, NULL);
+ if (err) {
+ dev_err(dev, "Failed to init plane: %i\n", err);
+ return err;
+ }
+
+ /*
+ * Sharpness settings range is [0,32]
+ * 0 : nearest-neighbor
+ * 1 : bilinear
+ * 2 .. 32 : bicubic (translated to sharpness factor -0.25 .. -4.0)
+ */
+ sharpness_max = soc_info->has_bicubic ? 32 : 1;
+ ipu->sharpness_prop = drm_property_create_range(drm, 0, "sharpness", 0,
+ sharpness_max);
+ if (!ipu->sharpness_prop) {
+ dev_err(dev, "Unable to create sharpness property\n");
+ return -ENOMEM;
+ }
+
+ /* Default sharpness factor: -0.125 * 8 = -1.0 */
+ ipu->sharpness = soc_info->has_bicubic ? 8 : 1;
+ drm_object_attach_property(&plane->base, ipu->sharpness_prop,
+ ipu->sharpness);
+
+ err = clk_prepare_enable(ipu->clk);
+ if (err) {
+ dev_err(dev, "Unable to enable clock\n");
+ return err;
+ }
+
+ return 0;
+}
+
+static void ingenic_ipu_unbind(struct device *dev,
+ struct device *master, void *d)
+{
+ struct ingenic_ipu *ipu = dev_get_drvdata(dev);
+
+ clk_disable_unprepare(ipu->clk);
+}
+
+static const struct component_ops ingenic_ipu_ops = {
+ .bind = ingenic_ipu_bind,
+ .unbind = ingenic_ipu_unbind,
+};
+
+static int ingenic_ipu_probe(struct platform_device *pdev)
+{
+ return component_add(&pdev->dev, &ingenic_ipu_ops);
+}
+
+static int ingenic_ipu_remove(struct platform_device *pdev)
+{
+ component_del(&pdev->dev, &ingenic_ipu_ops);
+ return 0;
+}
+
+static const u32 jz4725b_ipu_formats[] = {
+ DRM_FORMAT_YUYV,
+ DRM_FORMAT_YVYU,
+ DRM_FORMAT_UYVY,
+ DRM_FORMAT_VYUY,
+ DRM_FORMAT_YUV411,
+ DRM_FORMAT_YUV420,
+ DRM_FORMAT_YUV422,
+ DRM_FORMAT_YUV444,
+};
+
+static const struct soc_info jz4725b_soc_info = {
+ .formats = jz4725b_ipu_formats,
+ .num_formats = ARRAY_SIZE(jz4725b_ipu_formats),
+ .has_bicubic = false,
+ .set_coefs = jz4725b_set_coefs,
+};
+
+static const u32 jz4760_ipu_formats[] = {
+ DRM_FORMAT_XRGB1555,
+ DRM_FORMAT_XBGR1555,
+ DRM_FORMAT_RGB565,
+ DRM_FORMAT_BGR565,
+ DRM_FORMAT_XRGB8888,
+ DRM_FORMAT_XBGR8888,
+ DRM_FORMAT_YUYV,
+ DRM_FORMAT_YVYU,
+ DRM_FORMAT_UYVY,
+ DRM_FORMAT_VYUY,
+ DRM_FORMAT_YUV411,
+ DRM_FORMAT_YUV420,
+ DRM_FORMAT_YUV422,
+ DRM_FORMAT_YUV444,
+};
+
+static const struct soc_info jz4760_soc_info = {
+ .formats = jz4760_ipu_formats,
+ .num_formats = ARRAY_SIZE(jz4760_ipu_formats),
+ .has_bicubic = true,
+ .set_coefs = jz4760_set_coefs,
+};
+
+static const struct of_device_id ingenic_ipu_of_match[] = {
+ { .compatible = "ingenic,jz4725b-ipu", .data = &jz4725b_soc_info },
+ { .compatible = "ingenic,jz4760-ipu", .data = &jz4760_soc_info },
+ { /* sentinel */ },
+};
+MODULE_DEVICE_TABLE(of, ingenic_ipu_of_match);
+
+static struct platform_driver ingenic_ipu_driver = {
+ .driver = {
+ .name = "ingenic-ipu",
+ .of_match_table = ingenic_ipu_of_match,
+ },
+ .probe = ingenic_ipu_probe,
+ .remove = ingenic_ipu_remove,
+};
+module_platform_driver(ingenic_ipu_driver);
+
+MODULE_AUTHOR("Paul Cercueil <paul@crapouillou.net>");
+MODULE_DESCRIPTION("IPU driver for the Ingenic SoCs\n");
+MODULE_LICENSE("GPL v2");
diff --git a/drivers/gpu/drm/ingenic/ingenic-ipu.h b/drivers/gpu/drm/ingenic/ingenic-ipu.h
new file mode 100644
index 000000000000..31e1088cf5b0
--- /dev/null
+++ b/drivers/gpu/drm/ingenic/ingenic-ipu.h
@@ -0,0 +1,110 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * Ingenic JZ47xx IPU - Register definitions and private API
+ *
+ * Copyright (C) 2020, Paul Cercueil <paul@crapouillou.net>
+ */
+
+#ifndef DRIVERS_GPU_DRM_INGENIC_INGENIC_IPU_H
+#define DRIVERS_GPU_DRM_INGENIC_INGENIC_IPU_H
+
+#include <linux/bitops.h>
+
+#define JZ_REG_IPU_CTRL 0x00
+#define JZ_REG_IPU_STATUS 0x04
+#define JZ_REG_IPU_D_FMT 0x08
+#define JZ_REG_IPU_Y_ADDR 0x0c
+#define JZ_REG_IPU_U_ADDR 0x10
+#define JZ_REG_IPU_V_ADDR 0x14
+#define JZ_REG_IPU_IN_GS 0x18
+#define JZ_REG_IPU_Y_STRIDE 0x1c
+#define JZ_REG_IPU_UV_STRIDE 0x20
+#define JZ_REG_IPU_OUT_ADDR 0x24
+#define JZ_REG_IPU_OUT_GS 0x28
+#define JZ_REG_IPU_OUT_STRIDE 0x2c
+#define JZ_REG_IPU_RSZ_COEF_INDEX 0x30
+#define JZ_REG_IPU_CSC_C0_COEF 0x34
+#define JZ_REG_IPU_CSC_C1_COEF 0x38
+#define JZ_REG_IPU_CSC_C2_COEF 0x3c
+#define JZ_REG_IPU_CSC_C3_COEF 0x40
+#define JZ_REG_IPU_CSC_C4_COEF 0x44
+#define JZ_REG_IPU_HRSZ_COEF_LUT 0x48
+#define JZ_REG_IPU_VRSZ_COEF_LUT 0x4c
+#define JZ_REG_IPU_CSC_OFFSET 0x50
+#define JZ_REG_IPU_Y_PHY_T_ADDR 0x54
+#define JZ_REG_IPU_U_PHY_T_ADDR 0x58
+#define JZ_REG_IPU_V_PHY_T_ADDR 0x5c
+#define JZ_REG_IPU_OUT_PHY_T_ADDR 0x60
+
+#define JZ_IPU_CTRL_ADDR_SEL BIT(20)
+#define JZ_IPU_CTRL_ZOOM_SEL BIT(18)
+#define JZ_IPU_CTRL_DFIX_SEL BIT(17)
+#define JZ_IPU_CTRL_LCDC_SEL BIT(11)
+#define JZ_IPU_CTRL_SPKG_SEL BIT(10)
+#define JZ_IPU_CTRL_VSCALE BIT(9)
+#define JZ_IPU_CTRL_HSCALE BIT(8)
+#define JZ_IPU_CTRL_STOP BIT(7)
+#define JZ_IPU_CTRL_RST BIT(6)
+#define JZ_IPU_CTRL_FM_IRQ_EN BIT(5)
+#define JZ_IPU_CTRL_CSC_EN BIT(4)
+#define JZ_IPU_CTRL_VRSZ_EN BIT(3)
+#define JZ_IPU_CTRL_HRSZ_EN BIT(2)
+#define JZ_IPU_CTRL_RUN BIT(1)
+#define JZ_IPU_CTRL_CHIP_EN BIT(0)
+
+#define JZ_IPU_STATUS_OUT_END BIT(0)
+
+#define JZ_IPU_IN_GS_H_LSB 0x0
+#define JZ_IPU_IN_GS_W_LSB 0x10
+#define JZ_IPU_OUT_GS_H_LSB 0x0
+#define JZ_IPU_OUT_GS_W_LSB 0x10
+
+#define JZ_IPU_Y_STRIDE_Y_LSB 0
+#define JZ_IPU_UV_STRIDE_U_LSB 16
+#define JZ_IPU_UV_STRIDE_V_LSB 0
+
+#define JZ_IPU_D_FMT_IN_FMT_LSB 0
+#define JZ_IPU_D_FMT_IN_FMT_RGB555 (0x0 << JZ_IPU_D_FMT_IN_FMT_LSB)
+#define JZ_IPU_D_FMT_IN_FMT_YUV420 (0x0 << JZ_IPU_D_FMT_IN_FMT_LSB)
+#define JZ_IPU_D_FMT_IN_FMT_YUV422 (0x1 << JZ_IPU_D_FMT_IN_FMT_LSB)
+#define JZ_IPU_D_FMT_IN_FMT_RGB888 (0x2 << JZ_IPU_D_FMT_IN_FMT_LSB)
+#define JZ_IPU_D_FMT_IN_FMT_YUV444 (0x2 << JZ_IPU_D_FMT_IN_FMT_LSB)
+#define JZ_IPU_D_FMT_IN_FMT_RGB565 (0x3 << JZ_IPU_D_FMT_IN_FMT_LSB)
+
+#define JZ_IPU_D_FMT_YUV_FMT_LSB 2
+#define JZ_IPU_D_FMT_YUV_Y1UY0V (0x0 << JZ_IPU_D_FMT_YUV_FMT_LSB)
+#define JZ_IPU_D_FMT_YUV_Y1VY0U (0x1 << JZ_IPU_D_FMT_YUV_FMT_LSB)
+#define JZ_IPU_D_FMT_YUV_UY1VY0 (0x2 << JZ_IPU_D_FMT_YUV_FMT_LSB)
+#define JZ_IPU_D_FMT_YUV_VY1UY0 (0x3 << JZ_IPU_D_FMT_YUV_FMT_LSB)
+#define JZ_IPU_D_FMT_IN_FMT_YUV411 (0x3 << JZ_IPU_D_FMT_IN_FMT_LSB)
+
+#define JZ_IPU_D_FMT_OUT_FMT_LSB 19
+#define JZ_IPU_D_FMT_OUT_FMT_RGB555 (0x0 << JZ_IPU_D_FMT_OUT_FMT_LSB)
+#define JZ_IPU_D_FMT_OUT_FMT_RGB565 (0x1 << JZ_IPU_D_FMT_OUT_FMT_LSB)
+#define JZ_IPU_D_FMT_OUT_FMT_RGB888 (0x2 << JZ_IPU_D_FMT_OUT_FMT_LSB)
+#define JZ_IPU_D_FMT_OUT_FMT_YUV422 (0x3 << JZ_IPU_D_FMT_OUT_FMT_LSB)
+#define JZ_IPU_D_FMT_OUT_FMT_RGBAAA (0x4 << JZ_IPU_D_FMT_OUT_FMT_LSB)
+
+#define JZ_IPU_D_FMT_RGB_OUT_OFT_LSB 22
+#define JZ_IPU_D_FMT_RGB_OUT_OFT_RGB (0x0 << JZ_IPU_D_FMT_RGB_OUT_OFT_LSB)
+#define JZ_IPU_D_FMT_RGB_OUT_OFT_RBG (0x1 << JZ_IPU_D_FMT_RGB_OUT_OFT_LSB)
+#define JZ_IPU_D_FMT_RGB_OUT_OFT_GBR (0x2 << JZ_IPU_D_FMT_RGB_OUT_OFT_LSB)
+#define JZ_IPU_D_FMT_RGB_OUT_OFT_GRB (0x3 << JZ_IPU_D_FMT_RGB_OUT_OFT_LSB)
+#define JZ_IPU_D_FMT_RGB_OUT_OFT_BRG (0x4 << JZ_IPU_D_FMT_RGB_OUT_OFT_LSB)
+#define JZ_IPU_D_FMT_RGB_OUT_OFT_BGR (0x5 << JZ_IPU_D_FMT_RGB_OUT_OFT_LSB)
+
+#define JZ4725B_IPU_RSZ_LUT_COEF_LSB 2
+#define JZ4725B_IPU_RSZ_LUT_COEF_MASK 0x7ff
+#define JZ4725B_IPU_RSZ_LUT_IN_EN BIT(1)
+#define JZ4725B_IPU_RSZ_LUT_OUT_EN BIT(0)
+
+#define JZ4760_IPU_RSZ_COEF20_LSB 6
+#define JZ4760_IPU_RSZ_COEF31_LSB 17
+#define JZ4760_IPU_RSZ_COEF_MASK 0x7ff
+#define JZ4760_IPU_RSZ_OFFSET_LSB 1
+#define JZ4760_IPU_RSZ_OFFSET_MASK 0x1f
+
+#define JZ_IPU_CSC_OFFSET_CHROMA_LSB 16
+#define JZ_IPU_CSC_OFFSET_LUMA_LSB 16
+
+#endif /* DRIVERS_GPU_DRM_INGENIC_INGENIC_IPU_H */
--
2.26.2
^ permalink raw reply related
* [PATCH 10/12] gpu/drm: Ingenic: Register driver as component master
From: Paul Cercueil @ 2020-05-16 21:50 UTC (permalink / raw)
To: David Airlie, Daniel Vetter, Rob Herring, Greg Kroah-Hartman,
Rafael J . Wysocki
Cc: od, dri-devel, devicetree, linux-kernel, Paul Cercueil
In-Reply-To: <20200516215057.392609-1-paul@crapouillou.net>
Register the ingenic-drm driver as a component master.
This will later allow to plug optional components to the driver, for
instance to add support for the IPU, or the SLCD module.
Signed-off-by: Paul Cercueil <paul@crapouillou.net>
---
drivers/gpu/drm/ingenic/ingenic-drm.c | 61 +++++++++++++++++++++++++--
1 file changed, 57 insertions(+), 4 deletions(-)
diff --git a/drivers/gpu/drm/ingenic/ingenic-drm.c b/drivers/gpu/drm/ingenic/ingenic-drm.c
index f139af5b7a40..b53db3ee001f 100644
--- a/drivers/gpu/drm/ingenic/ingenic-drm.c
+++ b/drivers/gpu/drm/ingenic/ingenic-drm.c
@@ -6,6 +6,7 @@
#include "ingenic-drm.h"
+#include <linux/component.h>
#include <linux/clk.h>
#include <linux/dma-mapping.h>
#include <linux/module.h>
@@ -613,10 +614,17 @@ static void ingenic_drm_free_dma_hwdesc(void *d)
priv->dma_hwdesc[1], priv->dma_hwdesc_phys[1]);
}
-static int ingenic_drm_probe(struct platform_device *pdev)
+static void ingenic_drm_unbind_all(void *d)
{
+ struct ingenic_drm *priv = d;
+
+ component_unbind_all(priv->dev, &priv->drm);
+}
+
+static int ingenic_drm_bind(struct device *dev)
+{
+ struct platform_device *pdev = to_platform_device(dev);
const struct jz_soc_info *soc_info;
- struct device *dev = &pdev->dev;
struct ingenic_drm *priv;
struct clk *parent_clk;
struct drm_bridge *bridge;
@@ -653,6 +661,17 @@ static int ingenic_drm_probe(struct platform_device *pdev)
drm->mode_config.max_height = 4095;
drm->mode_config.funcs = &ingenic_drm_mode_config_funcs;
+ ret = component_bind_all(dev, drm);
+ if (ret) {
+ if (ret != -EPROBE_DEFER)
+ dev_err(dev, "Failed to bind components: %i", ret);
+ return ret;
+ }
+
+ ret = devm_add_action_or_reset(dev, ingenic_drm_unbind_all, priv);
+ if (ret)
+ return ret;
+
base = devm_platform_ioremap_resource(pdev, 0);
if (IS_ERR(base)) {
dev_err(dev, "Failed to get memory resource");
@@ -843,9 +862,14 @@ static int ingenic_drm_probe(struct platform_device *pdev)
return ret;
}
-static int ingenic_drm_remove(struct platform_device *pdev)
+static int compare_of(struct device *dev, void *data)
+{
+ return dev->of_node == data;
+}
+
+static void ingenic_drm_unbind(struct device *dev)
{
- struct ingenic_drm *priv = platform_get_drvdata(pdev);
+ struct ingenic_drm *priv = dev_get_drvdata(dev);
if (priv->lcd_clk)
clk_disable_unprepare(priv->lcd_clk);
@@ -853,6 +877,35 @@ static int ingenic_drm_remove(struct platform_device *pdev)
drm_dev_unregister(&priv->drm);
drm_atomic_helper_shutdown(&priv->drm);
+}
+
+static const struct component_master_ops ingenic_master_ops = {
+ .bind = ingenic_drm_bind,
+ .unbind = ingenic_drm_unbind,
+};
+
+static int ingenic_drm_probe(struct platform_device *pdev)
+{
+ struct device *dev = &pdev->dev;
+ struct component_match *match = NULL;
+ struct device_node *np;
+ unsigned int i;
+
+ /* Probe components at port address 8 and upwards */
+ for (i = 8; ; i++) {
+ np = of_graph_get_remote_node(dev->of_node, i, 0);
+ if (!np)
+ break;
+
+ drm_of_component_match_add(dev, &match, compare_of, np);
+ }
+
+ return component_master_add_with_match(dev, &ingenic_master_ops, match);
+}
+
+static int ingenic_drm_remove(struct platform_device *pdev)
+{
+ component_master_del(&pdev->dev, &ingenic_master_ops);
return 0;
}
--
2.26.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