Linux-ARM-Kernel Archive on lore.kernel.org
 help / color / mirror / Atom feed
* [PATCH v4] devicetree: bindings: uart: Add new compatible string for ZynqMP
From: Nava kishore Manne @ 2016-09-26 12:49 UTC (permalink / raw)
  To: linux-arm-kernel
In-Reply-To: <20160923223747.GA1163@rob-hp-laptop>



> -----Original Message-----
> From: Rob Herring [mailto:robh at kernel.org]
> Sent: Saturday, September 24, 2016 4:08 AM
> To: Nava kishore Manne <navam@xilinx.com>
> Cc: gregkh at linuxfoundation.org; mark.rutland at arm.com; jslaby at suse.com;
> michal.simek at xilinx.com; Soren Brinkmann <sorenb@xilinx.com>; linux-
> serial at vger.kernel.org; devicetree at vger.kernel.org; linux-
> kernel at vger.kernel.org; linux-arm-kernel at lists.infradead.org; Punnaiah
> Choudary Kalluri <punnaia@xilinx.com>; Nava kishore Manne
> <navam@xilinx.com>
> Subject: Re: [PATCH v4] devicetree: bindings: uart: Add new compatible
> string for ZynqMP
>
> On Fri, Sep 23, 2016 at 04:55:48PM +0530, Nava kishore Manne wrote:
> > From: Nava kishore Manne <nava.manne@xilinx.com>
> >
> > This patch Adds the new compatible string for ZynqMP.
> >
> > Signed-off-by: Nava kishore Manne <navam@xilinx.com>
> > ---
> > Changes for v4:
> >             -Modified the ChangeLog comment.
> > Changes for v3:
> >             -Added changeLog comment.
> > Changes for v2:
> >                 -None
> >
> >  Documentation/devicetree/bindings/serial/cdns,uart.txt | 3 ++-
> >  1 file changed, 2 insertions(+), 1 deletion(-)
> >
> > diff --git a/Documentation/devicetree/bindings/serial/cdns,uart.txt
> b/Documentation/devicetree/bindings/serial/cdns,uart.txt
> > index a3eb154..4fe7aae 100644
> > --- a/Documentation/devicetree/bindings/serial/cdns,uart.txt
> > +++ b/Documentation/devicetree/bindings/serial/cdns,uart.txt
> > @@ -1,7 +1,8 @@
> >  Binding for Cadence UART Controller
> >
> >  Required properties:
> > -- compatible : should be "cdns,uart-r1p8", or "xlnx,xuartps"
> > +- compatible : should be "cdns,uart-r1p8", or "xlnx,xuartps" for Zynq and
> > +               "cdns,uart-r1p12" for Zynq Ultrascale+ MPSoC uart controller.
>
> Please format this as one valid combination per line.
>
> xuartps is not very specific. It needs some abbreviation of "Zynq
> Ultrascale+ MPSoC" in it.
>

Will fix the above comments in the next version.

Regards,
Navakishore.


This email and any attachments are intended for the sole use of the named recipient(s) and contain(s) confidential information that may be proprietary, privileged or copyrighted under applicable law. If you are not the intended recipient, do not read, copy, or forward this email message or any attachments. Delete this email message and any attachments immediately.

^ permalink raw reply

* [PATCH 2/2] drivers: cci: add missing CCI port availability firmware check
From: Jon Medhurst (Tixy) @ 2016-09-26 12:30 UTC (permalink / raw)
  To: linux-arm-kernel
In-Reply-To: <20160923130907.4187-2-lorenzo.pieralisi@arm.com>

On Fri, 2016-09-23 at 14:09 +0100, Lorenzo Pieralisi wrote:
> The CCI ports programming interface is available to the kernel
> only when booted in secure mode (or when firmware enables
> non-secure access to override CCI ports control). In both cases,
> firmware reports the CCI ports availability through the device
> tree CCI ports nodes, which must be parsed and their status checked
> by the kernel probing path.
> 
> This check is currently missing and may cause the kernel to
> erroneously believe it is free to take control of CCI ports
> where in practice CCI ports control is forbidden.
> 
> Add the missing CCI port availability check to the CCI driver
> in order to guarantee sane CCI usage.
> 
> Signed-off-by: Lorenzo Pieralisi <lorenzo.pieralisi@arm.com>
> Cc: Liviu Dudau <liviu.dudau@arm.com>
> Cc: Sudeep Holla <sudeep.holla@arm.com>
> Cc: Nicolas Pitre <nicolas.pitre@linaro.org>
> Cc: Suzuki K Poulose <suzuki.poulose@arm.com>
> Cc: Marc Zyngier <marc.zyngier@arm.com>
> ---

Tested-by: Jon Medhurst <tixy@linaro.org>

>  drivers/bus/arm-cci.c | 10 ++++++++++
>  1 file changed, 10 insertions(+)
> 
> diff --git a/drivers/bus/arm-cci.c b/drivers/bus/arm-cci.c
> index 5755907f..3c7a1c7 100644
> --- a/drivers/bus/arm-cci.c
> +++ b/drivers/bus/arm-cci.c
> @@ -2199,6 +2199,9 @@ static int cci_probe_ports(struct device_node *np)
>  		if (!of_match_node(arm_cci_ctrl_if_matches, cp))
>  			continue;
>  
> +		if (!of_device_is_available(cp))
> +			continue;
> +
>  		i = nb_ace + nb_ace_lite;
>  
>  		if (i >= nb_cci_ports)
> @@ -2241,6 +2244,13 @@ static int cci_probe_ports(struct device_node *np)
>  		ports[i].dn = cp;
>  	}
>  
> +	/*
> +	 * If there is no CCI port that is under kernel control
> +	 * return early and report probe status.
> +	 */
> +	if (!nb_ace && !nb_ace_lite)
> +		return -ENODEV;
> +
>  	 /* initialize a stashed array of ACE ports to speed-up look-up */
>  	cci_ace_init_ports();
>  

^ permalink raw reply

* [PATCH 1/2] ARM: vexpress: refine MCPM smp operations override criteria
From: Jon Medhurst (Tixy) @ 2016-09-26 12:29 UTC (permalink / raw)
  To: linux-arm-kernel
In-Reply-To: <20160923130907.4187-1-lorenzo.pieralisi@arm.com>

On Fri, 2016-09-23 at 14:09 +0100, Lorenzo Pieralisi wrote:
> Current vexpress smp init code detects whether to override the
> default smp ops with MCPM smp ops by matching the "cci-400"
> compatible string, in that MCPM requires control over CCI ports
> to manage low-power states entry/exit.
> 
> The "cci-400" compatible string check is a necessary but not
> sufficient condition for MCPM to work, because the cci-400
> can be made visible to the kernel, but firmware can nonetheless
> disable non-secure CCI ports control, while still allowing PMU
> access; if booted in non-secure world, the kernel would still
> blindly override smp operations with MCPM operations, resulting
> in kernel faults when the CCI ports programming interface is
> accessed from non-secure world.
> 
> This means that the "cci-400" compatible string check would
> result in a false positive in systems that eg boot in HYP mode,
> where CCI ports non-secure access is explicitly not allowed,
> and it is reported in the respective device tree nodes with
> CCI ports marked as disabled.
> 
> Refactor the smp operations initialization to make sure that
> the kernel is actually allowed to take control over CCI ports
> (by enabling MCPM smp operations) before overriding default
> vexpress smp operations.
> 
> Signed-off-by: Lorenzo Pieralisi <lorenzo.pieralisi@arm.com>
> Cc: Liviu Dudau <liviu.dudau@arm.com>
> Cc: Sudeep Holla <sudeep.holla@arm.com>
> Cc: Nicolas Pitre <nicolas.pitre@linaro.org>
> Cc: Marc Zyngier <marc.zyngier@arm.com>
> ---

Tested-by: Jon Medhurst <tixy@linaro.org>

>  arch/arm/mach-vexpress/platsmp.c | 31 ++++++++++++++++++++++++-------
>  1 file changed, 24 insertions(+), 7 deletions(-)
> 
> diff --git a/arch/arm/mach-vexpress/platsmp.c b/arch/arm/mach-vexpress/platsmp.c
> index 8b8d072..6cfd782 100644
> --- a/arch/arm/mach-vexpress/platsmp.c
> +++ b/arch/arm/mach-vexpress/platsmp.c
> @@ -26,17 +26,34 @@
>  bool __init vexpress_smp_init_ops(void)
>  {
>  #ifdef CONFIG_MCPM
> +	int cpu;
> +	struct device_node *cpu_node, *cci_node;
> +
>  	/*
> -	 * The best way to detect a multi-cluster configuration at the moment
> -	 * is to look for the presence of a CCI in the system.
> +	 * The best way to detect a multi-cluster configuration
> +	 * is to detect if the kernel can take over CCI ports
> +	 * control. Loop over possible CPUs and check if CCI
> +	 * port control is available.
>  	 * Override the default vexpress_smp_ops if so.
>  	 */
> -	struct device_node *node;
> -	node = of_find_compatible_node(NULL, NULL, "arm,cci-400");
> -	if (node && of_device_is_available(node)) {
> -		mcpm_smp_set_ops();
> -		return true;
> +	for_each_possible_cpu(cpu) {
> +		bool available;
> +
> +		cpu_node = of_get_cpu_node(cpu, NULL);
> +		if (WARN(!cpu_node, "Missing cpu device node!"))
> +			return false;
> +
> +		cci_node = of_parse_phandle(cpu_node, "cci-control-port", 0);
> +		available = cci_node && of_device_is_available(cci_node);
> +		of_node_put(cci_node);
> +		of_node_put(cpu_node);
> +
> +		if (!available)
> +			return false;
>  	}
> +
> +	mcpm_smp_set_ops();
> +	return true;
>  #endif
>  	return false;
>  }

^ permalink raw reply

* [PATCH v7] KVM: arm/arm64: Route vtimer events to user space
From: Alexander Graf @ 2016-09-26 12:06 UTC (permalink / raw)
  To: linux-arm-kernel

We have 2 modes for dealing with interrupts in the ARM world. We can either
handle them all using hardware acceleration through the vgic or we can emulate
a gic in user space and only drive CPU IRQ pins from there.

Unfortunately, when driving IRQs from user space, we never tell user space
about timer events that may result in interrupt line state changes, so we
lose out on timer events if we run with user space gic emulation.

This patch fixes that by exposing kvm's view of the vtimer irq line to user
space and giving it a chance to sync that when it changes.

With this patch I can successfully run edk2 and Linux with user space gic
emulation.

Signed-off-by: Alexander Graf <agraf@suse.de>

---

v1 -> v2:

  - Add back curly brace that got lost

v2 -> v3:

  - Split into patch set

v3 -> v4:

  - Improve documentation

v4 -> v5:

  - Rewrite to use pending state sync in sregs (marc)
  - Remove redundant checks of vgic_initialized()

v5 -> v6:

  - s/pending/asserted/
  - split kvm_timer_flush_hwstate function
  - remove user_timer_asserted
  - rename kernel_timer_asserted to timer_irq_level
  - exit only once per level change

v6 -> v7:

  - Move run struct sync out of the run loop
  - Only check for run sync in kvm_timer_flush_hwstate()
  - qemu tree to try this out: https://github.com/agraf/qemu.git no-kvm-irqchip-for-v6
---
 Documentation/virtual/kvm/api.txt |  27 +++++++++
 arch/arm/include/uapi/asm/kvm.h   |   2 +
 arch/arm/kvm/arm.c                |  18 +++---
 arch/arm64/include/uapi/asm/kvm.h |   2 +
 include/kvm/arm_arch_timer.h      |   3 +-
 include/uapi/linux/kvm.h          |   6 ++
 virt/kvm/arm/arch_timer.c         | 122 ++++++++++++++++++++++++++++++++------
 7 files changed, 150 insertions(+), 30 deletions(-)

diff --git a/Documentation/virtual/kvm/api.txt b/Documentation/virtual/kvm/api.txt
index 739db9a..20ab558 100644
--- a/Documentation/virtual/kvm/api.txt
+++ b/Documentation/virtual/kvm/api.txt
@@ -3928,3 +3928,30 @@ In order to use SynIC, it has to be activated by setting this
 capability via KVM_ENABLE_CAP ioctl on the vcpu fd. Note that this
 will disable the use of APIC hardware virtualization even if supported
 by the CPU, as it's incompatible with SynIC auto-EOI behavior.
+
+8.3 KVM_CAP_ARM_TIMER
+
+Architectures: arm, arm64
+This capability, if KVM_CHECK_EXTENSION indicates that it is available and no
+in-kernel interrupt controller is in use, means that that the kernel populates
+the vcpu's run->s.regs.timer_irq_level field with timers that are currently
+considered asserted by kvm.
+
+If active, kvm guarantees at least one exit happens after it detects an
+assertion change. This exit could either be a KVM_EXIT_INTR or any other exit
+event, like KVM_EXIT_MMIO. This way kvm gives user space a chance to update its
+own interrupt asserted status. This usually involves triggering an interrupt
+line on a user space emulated interrupt controller, so user space should always
+check the state of run->s.regs.timer_irq_level on every kvm exit.
+
+The field run->s.regs.timer_irq_level is available independent of
+run->kvm_valid_regs or run->kvm_dirty_regs bits. If no in-kernel interrupt
+controller is used and the capability exists, it will always be available and
+populated.
+
+Currently the following bits are defined for the timer_irq_level bitmap:
+
+    KVM_ARM_TIMER_VTIMER  -  virtual timer
+
+Future versions of kvm may implement additional timer events. These will get
+indicated by additional KVM_CAP extensions.
diff --git a/arch/arm/include/uapi/asm/kvm.h b/arch/arm/include/uapi/asm/kvm.h
index a2b3eb3..ad61b4f 100644
--- a/arch/arm/include/uapi/asm/kvm.h
+++ b/arch/arm/include/uapi/asm/kvm.h
@@ -105,6 +105,8 @@ struct kvm_debug_exit_arch {
 };
 
 struct kvm_sync_regs {
+	/* Used with KVM_CAP_ARM_TIMER */
+	u8 timer_irq_level;
 };
 
 struct kvm_arch_memory_slot {
diff --git a/arch/arm/kvm/arm.c b/arch/arm/kvm/arm.c
index 75f130e..c4c1734 100644
--- a/arch/arm/kvm/arm.c
+++ b/arch/arm/kvm/arm.c
@@ -187,6 +187,7 @@ int kvm_vm_ioctl_check_extension(struct kvm *kvm, long ext)
 	case KVM_CAP_ARM_PSCI_0_2:
 	case KVM_CAP_READONLY_MEM:
 	case KVM_CAP_MP_STATE:
+	case KVM_CAP_ARM_TIMER:
 		r = 1;
 		break;
 	case KVM_CAP_COALESCED_MMIO:
@@ -474,13 +475,7 @@ static int kvm_vcpu_first_run_init(struct kvm_vcpu *vcpu)
 			return ret;
 	}
 
-	/*
-	 * Enable the arch timers only if we have an in-kernel VGIC
-	 * and it has been properly initialized, since we cannot handle
-	 * interrupts from the virtual timer with a userspace gic.
-	 */
-	if (irqchip_in_kernel(kvm) && vgic_initialized(kvm))
-		ret = kvm_timer_enable(vcpu);
+	ret = kvm_timer_enable(vcpu);
 
 	return ret;
 }
@@ -549,7 +544,7 @@ static int kvm_vcpu_initialized(struct kvm_vcpu *vcpu)
  */
 int kvm_arch_vcpu_ioctl_run(struct kvm_vcpu *vcpu, struct kvm_run *run)
 {
-	int ret;
+	int ret, timer_ret;
 	sigset_t sigsaved;
 
 	if (unlikely(!kvm_vcpu_initialized(vcpu)))
@@ -588,7 +583,7 @@ int kvm_arch_vcpu_ioctl_run(struct kvm_vcpu *vcpu, struct kvm_run *run)
 		 */
 		preempt_disable();
 		kvm_pmu_flush_hwstate(vcpu);
-		kvm_timer_flush_hwstate(vcpu);
+		timer_ret = kvm_timer_flush_hwstate(vcpu);
 		kvm_vgic_flush_hwstate(vcpu);
 
 		local_irq_disable();
@@ -596,7 +591,7 @@ int kvm_arch_vcpu_ioctl_run(struct kvm_vcpu *vcpu, struct kvm_run *run)
 		/*
 		 * Re-check atomic conditions
 		 */
-		if (signal_pending(current)) {
+		if (timer_ret || signal_pending(current)) {
 			ret = -EINTR;
 			run->exit_reason = KVM_EXIT_INTR;
 		}
@@ -668,6 +663,9 @@ int kvm_arch_vcpu_ioctl_run(struct kvm_vcpu *vcpu, struct kvm_run *run)
 		ret = handle_exit(vcpu, run, ret);
 	}
 
+	/* Resynchronize the internal timer and vcpu run irq level bits */
+	kvm_timer_sync_run(vcpu);
+
 	if (vcpu->sigset_active)
 		sigprocmask(SIG_SETMASK, &sigsaved, NULL);
 	return ret;
diff --git a/arch/arm64/include/uapi/asm/kvm.h b/arch/arm64/include/uapi/asm/kvm.h
index 3051f86..411d62a 100644
--- a/arch/arm64/include/uapi/asm/kvm.h
+++ b/arch/arm64/include/uapi/asm/kvm.h
@@ -143,6 +143,8 @@ struct kvm_debug_exit_arch {
 #define KVM_GUESTDBG_USE_HW		(1 << 17)
 
 struct kvm_sync_regs {
+	/* Used with KVM_CAP_ARM_TIMER */
+	u8 timer_irq_level;
 };
 
 struct kvm_arch_memory_slot {
diff --git a/include/kvm/arm_arch_timer.h b/include/kvm/arm_arch_timer.h
index dda39d8..cc49bcd 100644
--- a/include/kvm/arm_arch_timer.h
+++ b/include/kvm/arm_arch_timer.h
@@ -63,8 +63,9 @@ void kvm_timer_init(struct kvm *kvm);
 int kvm_timer_vcpu_reset(struct kvm_vcpu *vcpu,
 			 const struct kvm_irq_level *irq);
 void kvm_timer_vcpu_init(struct kvm_vcpu *vcpu);
-void kvm_timer_flush_hwstate(struct kvm_vcpu *vcpu);
+int kvm_timer_flush_hwstate(struct kvm_vcpu *vcpu);
 void kvm_timer_sync_hwstate(struct kvm_vcpu *vcpu);
+void kvm_timer_sync_run(struct kvm_vcpu *vcpu);
 void kvm_timer_vcpu_terminate(struct kvm_vcpu *vcpu);
 
 u64 kvm_arm_timer_get_reg(struct kvm_vcpu *, u64 regid);
diff --git a/include/uapi/linux/kvm.h b/include/uapi/linux/kvm.h
index 300ef25..10d63c5 100644
--- a/include/uapi/linux/kvm.h
+++ b/include/uapi/linux/kvm.h
@@ -870,6 +870,7 @@ struct kvm_ppc_smmu_info {
 #define KVM_CAP_S390_USER_INSTR0 130
 #define KVM_CAP_MSI_DEVID 131
 #define KVM_CAP_PPC_HTM 132
+#define KVM_CAP_ARM_TIMER 133
 
 #ifdef KVM_CAP_IRQ_ROUTING
 
@@ -1327,4 +1328,9 @@ struct kvm_assigned_msix_entry {
 #define KVM_X2APIC_API_USE_32BIT_IDS            (1ULL << 0)
 #define KVM_X2APIC_API_DISABLE_BROADCAST_QUIRK  (1ULL << 1)
 
+/* Available with KVM_CAP_ARM_TIMER */
+
+/* Bits for run->s.regs.{user,kernel}_timer_asserted */
+#define KVM_ARM_TIMER_VTIMER		(1 << 0)
+
 #endif /* __LINUX_KVM_H */
diff --git a/virt/kvm/arm/arch_timer.c b/virt/kvm/arm/arch_timer.c
index 4309b60..62f1daa 100644
--- a/virt/kvm/arm/arch_timer.c
+++ b/virt/kvm/arm/arch_timer.c
@@ -166,26 +166,69 @@ bool kvm_timer_should_fire(struct kvm_vcpu *vcpu)
 	return cval <= now;
 }
 
+static bool kvm_timer_in_sync_with_run(struct kvm_vcpu *vcpu)
+{
+	struct kvm_sync_regs *regs = &vcpu->run->s.regs;
+	bool timer_high = vcpu->arch.timer_cpu.irq.level;
+	bool run_high = regs->timer_irq_level & KVM_ARM_TIMER_VTIMER;
+
+	/* Only need to sync with user space irqchip */
+	if (irqchip_in_kernel(vcpu->kvm))
+		return true;
+
+	return timer_high == run_high;
+}
+
+/*
+ * Synchronize the vcpu run irq level bits with the timer
+ */
+void kvm_timer_sync_run(struct kvm_vcpu *vcpu)
+{
+	struct kvm_sync_regs *regs = &vcpu->run->s.regs;
+
+	/* Only synchronize if we're out of sync */
+	if (kvm_timer_in_sync_with_run(vcpu))
+		return;
+
+	/* Populate the timer bitmap for user space */
+	regs->timer_irq_level &= ~KVM_ARM_TIMER_VTIMER;
+	if (vcpu->arch.timer_cpu.irq.level)
+		regs->timer_irq_level |= KVM_ARM_TIMER_VTIMER;
+}
+
+/*
+ * Synchronize the timer IRQ state with the interrupt controller.
+ */
 static void kvm_timer_update_irq(struct kvm_vcpu *vcpu, bool new_level)
 {
 	int ret;
 	struct arch_timer_cpu *timer = &vcpu->arch.timer_cpu;
 
-	BUG_ON(!vgic_initialized(vcpu->kvm));
-
 	timer->active_cleared_last = false;
 	timer->irq.level = new_level;
-	trace_kvm_timer_update_irq(vcpu->vcpu_id, timer->irq.irq,
+	trace_kvm_timer_update_irq(vcpu->vcpu_id, host_vtimer_irq,
 				   timer->irq.level);
-	ret = kvm_vgic_inject_mapped_irq(vcpu->kvm, vcpu->vcpu_id,
-					 timer->irq.irq,
-					 timer->irq.level);
-	WARN_ON(ret);
+
+	if (irqchip_in_kernel(vcpu->kvm)) {
+		BUG_ON(!vgic_initialized(vcpu->kvm));
+
+		/* Fire the timer in the VGIC */
+		ret = kvm_vgic_inject_mapped_irq(vcpu->kvm, vcpu->vcpu_id,
+						 timer->irq.irq,
+						 timer->irq.level);
+
+		WARN_ON(ret);
+	}
 }
 
 /*
  * Check if there was a change in the timer state (should we raise or lower
  * the line level to the GIC).
+ *
+ * Returns:
+ *
+ *   <0  - error
+ *    0  - success
  */
 static int kvm_timer_update_state(struct kvm_vcpu *vcpu)
 {
@@ -197,7 +240,8 @@ static int kvm_timer_update_state(struct kvm_vcpu *vcpu)
 	 * because the guest would never see the interrupt.  Instead wait
 	 * until we call this function from kvm_timer_flush_hwstate.
 	 */
-	if (!vgic_initialized(vcpu->kvm) || !timer->enabled)
+	if ((irqchip_in_kernel(vcpu->kvm) && !vgic_initialized(vcpu->kvm)) ||
+	    !timer->enabled)
 		return -ENODEV;
 
 	if (kvm_timer_should_fire(vcpu) != timer->irq.level)
@@ -242,22 +286,12 @@ void kvm_timer_unschedule(struct kvm_vcpu *vcpu)
 	timer_disarm(timer);
 }
 
-/**
- * kvm_timer_flush_hwstate - prepare to move the virt timer to the cpu
- * @vcpu: The vcpu pointer
- *
- * Check if the virtual timer has expired while we were running in the host,
- * and inject an interrupt if that was the case.
- */
-void kvm_timer_flush_hwstate(struct kvm_vcpu *vcpu)
+static void kvm_timer_flush_hwstate_vgic(struct kvm_vcpu *vcpu)
 {
 	struct arch_timer_cpu *timer = &vcpu->arch.timer_cpu;
 	bool phys_active;
 	int ret;
 
-	if (kvm_timer_update_state(vcpu))
-		return;
-
 	/*
 	* If we enter the guest with the virtual input level to the VGIC
 	* asserted, then we have already told the VGIC what we need to, and
@@ -309,6 +343,51 @@ void kvm_timer_flush_hwstate(struct kvm_vcpu *vcpu)
 	timer->active_cleared_last = !phys_active;
 }
 
+static void kvm_timer_flush_hwstate_user(struct kvm_vcpu *vcpu)
+{
+	struct arch_timer_cpu *timer = &vcpu->arch.timer_cpu;
+
+	/*
+	 * As long as the timer is asserted, we do not need to get new host
+	 * timer events to know whether the line is still up. Since the GIC
+	 * is handled in user space, we will get an exit on EOI, so we can
+	 * resynchronize the status there.
+	 */
+	if (timer->irq.level)
+		disable_percpu_irq(host_vtimer_irq);
+	else
+		enable_percpu_irq(host_vtimer_irq, 0);
+}
+
+/**
+ * kvm_timer_flush_hwstate - prepare to move the virt timer to the cpu
+ * @vcpu: The vcpu pointer
+ *
+ * Check if the virtual timer has expired while we were running in the host,
+ * and inject an interrupt if that was the case.
+ *
+ * Returns:
+ *
+ *    0  - success
+ *    1  - need exit to user space
+ */
+int kvm_timer_flush_hwstate(struct kvm_vcpu *vcpu)
+{
+	if (kvm_timer_update_state(vcpu))
+		return 0;
+
+	/* Do we need to return to user space to resynchronize? */
+	if (!kvm_timer_in_sync_with_run(vcpu))
+		return 1;
+
+	if (irqchip_in_kernel(vcpu->kvm))
+		kvm_timer_flush_hwstate_vgic(vcpu);
+	else
+		kvm_timer_flush_hwstate_user(vcpu);
+
+	return 0;
+}
+
 /**
  * kvm_timer_sync_hwstate - sync timer state from cpu
  * @vcpu: The vcpu pointer
@@ -479,6 +558,10 @@ int kvm_timer_enable(struct kvm_vcpu *vcpu)
 	if (timer->enabled)
 		return 0;
 
+	/* No need to route physical IRQs when we don't use the vgic */
+	if (!irqchip_in_kernel(vcpu->kvm))
+		goto no_vgic;
+
 	/*
 	 * Find the physical IRQ number corresponding to the host_vtimer_irq
 	 */
@@ -502,6 +585,7 @@ int kvm_timer_enable(struct kvm_vcpu *vcpu)
 	if (ret)
 		return ret;
 
+no_vgic:
 
 	/*
 	 * There is a potential race here between VCPUs starting for the first
-- 
1.8.5.6

^ permalink raw reply related

* [PATCH] serial: stm32: mark symbols static where possible
From: Baoyou Xie @ 2016-09-26 11:58 UTC (permalink / raw)
  To: linux-arm-kernel

We get 2 warnings when building kernel with W=1:
drivers/tty/serial/stm32-usart.c:63:5: warning: no previous prototype for 'stm32_pending_rx' [-Wmissing-prototypes]
drivers/tty/serial/stm32-usart.c:88:15: warning: no previous prototype for 'stm32_get_char' [-Wmissing-prototypes]

In fact, these two functions are only used in the file in which they are
declared and don't need a declaration, but can be made static.

So this patch marks these functions with 'static'.

Signed-off-by: Baoyou Xie <baoyou.xie@linaro.org>
---
 drivers/tty/serial/stm32-usart.c | 7 ++++---
 1 file changed, 4 insertions(+), 3 deletions(-)

diff --git a/drivers/tty/serial/stm32-usart.c b/drivers/tty/serial/stm32-usart.c
index 4d3001b..bb9b5e9 100644
--- a/drivers/tty/serial/stm32-usart.c
+++ b/drivers/tty/serial/stm32-usart.c
@@ -60,8 +60,8 @@ static void stm32_clr_bits(struct uart_port *port, u32 reg, u32 bits)
 	writel_relaxed(val, port->membase + reg);
 }
 
-int stm32_pending_rx(struct uart_port *port, u32 *sr, int *last_res,
-		     bool threaded)
+static int stm32_pending_rx(struct uart_port *port, u32 *sr, int *last_res,
+			    bool threaded)
 {
 	struct stm32_port *stm32_port = to_stm32_port(port);
 	struct stm32_usart_offsets *ofs = &stm32_port->info->ofs;
@@ -85,7 +85,8 @@ int stm32_pending_rx(struct uart_port *port, u32 *sr, int *last_res,
 	return 0;
 }
 
-unsigned long stm32_get_char(struct uart_port *port, u32 *sr, int *last_res)
+static unsigned long
+stm32_get_char(struct uart_port *port, u32 *sr, int *last_res)
 {
 	struct stm32_port *stm32_port = to_stm32_port(port);
 	struct stm32_usart_offsets *ofs = &stm32_port->info->ofs;
-- 
2.7.4

^ permalink raw reply related

* [PATCHv2 1/3] serial: mctrl_gpio: implement mctrl_gpio_use_rtscts
From: Richard Genoud @ 2016-09-26 11:57 UTC (permalink / raw)
  To: linux-arm-kernel
In-Reply-To: <20160922131405.y4wvgd3n534jbdv5@pengutronix.de>

2016-09-22 15:14 GMT+02:00 Uwe Kleine-K?nig <u.kleine-koenig@pengutronix.de>:
> On Mon, Sep 12, 2016 at 11:47:31AM +0200, Richard Genoud wrote:
>> This function returns true if CTS and RTS are used as GPIOs.
>> Some drivers (like atmel_serial) needs to know if the flow control is
>> handled by the controller or by GPIOs.
>>
>> Signed-off-by: Richard Genoud <richard.genoud@gmail.com>
>> ---
>>  drivers/tty/serial/serial_mctrl_gpio.c |  8 ++++++++
>>  drivers/tty/serial/serial_mctrl_gpio.h | 10 ++++++++++
>>  2 files changed, 18 insertions(+)
>>
>> diff --git a/drivers/tty/serial/serial_mctrl_gpio.c b/drivers/tty/serial/serial_mctrl_gpio.c
>> index d2da6aa7f27d..93bed8c99796 100644
>> --- a/drivers/tty/serial/serial_mctrl_gpio.c
>> +++ b/drivers/tty/serial/serial_mctrl_gpio.c
>> @@ -17,6 +17,7 @@
>>  #include <linux/err.h>
>>  #include <linux/device.h>
>>  #include <linux/irq.h>
>> +#include <linux/err.h>
>>  #include <linux/gpio/consumer.h>
>>  #include <linux/termios.h>
>>  #include <linux/serial_core.h>
>> @@ -72,6 +73,13 @@ struct gpio_desc *mctrl_gpio_to_gpiod(struct mctrl_gpios *gpios,
>>  }
>>  EXPORT_SYMBOL_GPL(mctrl_gpio_to_gpiod);
>>
>> +bool mctrl_gpio_use_rtscts(struct mctrl_gpios *gpios)
>> +{
>> +     return !IS_ERR_OR_NULL(mctrl_gpio_to_gpiod(gpios, UART_GPIO_CTS)) &&
>> +             !IS_ERR_OR_NULL(mctrl_gpio_to_gpiod(gpios, UART_GPIO_RTS));
>
> mctrl_gpio_to_gpiod cannot return an error pointer. So this is
> (surprise!) a wrong usage of IS_ERR_OR_NULL.

I see, your commit 1d267ea6539f266352f  ("serial: mctrl-gpio: simplify
init routine") removed the need of IS_ERR_OR_NULL().
I'll change that.

Thanks !

Richard.

^ permalink raw reply

* Re: [PATCH V5 3/4] drm/bridge: Add driver for GE B850v3 LVDS/DP++ Bridge
From: Peter Senna Tschudin @ 2016-09-26 11:54 UTC (permalink / raw)
  To: linux-arm-kernel
In-Reply-To: <180ae266-a4fb-4432-83dc-e39f2ccb0ad1@codeaurora.org>

 
On Monday, September 26, 2016 12:29 CEST, Archit Taneja <architt@codeaurora.org> wrote: 
 
> Hi,
> 
> Some comments.

Thank you for the review!

> 
> On 08/09/2016 10:11 PM, Peter Senna Tschudin wrote:
> > Add a driver that create a drm_bridge and a drm_connector for the LVDS
> > to DP++ display bridge of the GE B850v3.
> >
> > There are two physical bridges on the video signal pipeline: a
> > STDP4028(LVDS to DP) and a STDP2690(DP to DP++).  The hardware and

> > firmware made it complicated for this binding to comprise two device
> > tree nodes, as the design goal is to configure both bridges based on
> > the LVDS signal, which leave the driver powerless to control the video
> > processing pipeline. The two bridges behaves as a single bridge, and
> > the driver is only needed for telling the host about EDID / HPD, and
> > for giving the host powers to ack interrupts. The video signal pipeline
> > is as follows:
> >
> >   Host -> LVDS|--(STDP4028)--|DP -> DP|--(STDP2690)--|DP++ -> Video output
> >
> > Cc: Martyn Welch <martyn.welch@collabora.co.uk>
> > Cc: Martin Donnelly <martin.donnelly@ge.com>
> > Cc: Daniel Vetter <daniel.vetter@ffwll.ch>
> > Cc: Enric Balletbo i Serra <enric.balletbo@collabora.com>
> > Cc: Philipp Zabel <p.zabel@pengutronix.de>
> > Cc: Rob Herring <robh@kernel.org>
> > Cc: Fabio Estevam <fabio.estevam@nxp.com>
> > CC: David Airlie <airlied@linux.ie>
> > CC: Thierry Reding <treding@nvidia.com>
> > CC: Thierry Reding <thierry.reding@gmail.com>
> > Reviewed-by: Enric Balletbo <enric.balletbo@collabora.com>
> > Signed-off-by: Peter Senna Tschudin <peter.senna@collabora.com>
> > ---
> > Changes from V4:
> >  - Check the output of the first call to i2c_smbus_write_word_data() and return
> >    it's error code for failing gracefully on i2c issues
> >  - Renamed the i2c_driver.name from "ge,b850v3-lvds-dp" to "b850v3-lvds-dp" to
> >    remove the comma from the driver name
> >
> > Changes from V3:
> >  - 3/4 instead of 4/5
> >  - Tested on next-20160804
> >
> > Changes from V2:
> >  - Made it atomic to be applied on next-20160729 on top of Liu Ying changes
> >    that made imx-ldb atomic
> >
> > Changes from V1:
> >  - New commit message
> >  - Removed 3 empty entry points
> >  - Removed memory leak from ge_b850v3_lvds_dp_get_modes()
> >  - Added a lock for mode setting
> >  - Removed a few blank lines
> >  - Changed the order at Makefile and Kconfig
> >
> >  MAINTAINERS                                |   8 +
> >  drivers/gpu/drm/bridge/Kconfig             |  11 +
> >  drivers/gpu/drm/bridge/Makefile            |   1 +
> >  drivers/gpu/drm/bridge/ge_b850v3_lvds_dp.c | 405 +++++++++++++++++++++++++++++
> >  4 files changed, 425 insertions(+)
> >  create mode 100644 drivers/gpu/drm/bridge/ge_b850v3_lvds_dp.c
> >
> > diff --git a/MAINTAINERS b/MAINTAINERS
> > index a306795..e8d106a 100644
> > --- a/MAINTAINERS
> > +++ b/MAINTAINERS
> > @@ -5142,6 +5142,14 @@ W:	https://linuxtv.org
> >  S:	Maintained
> >  F:	drivers/media/radio/radio-gemtek*
> >
> > +GENERAL ELECTRIC B850V3 LVDS/DP++ BRIDGE
> > +M:	Peter Senna Tschudin <peter.senna@collabora.com>
> > +M:	Martin Donnelly <martin.donnelly@ge.com>
> > +M:	Martyn Welch <martyn.welch@collabora.co.uk>
> > +S:	Maintained
> > +F:	drivers/gpu/drm/bridge/ge_b850v3_dp2.c
> > +F:	Documentation/devicetree/bindings/ge/b850v3_dp2_bridge.txt

> > +
> >  GENERIC GPIO I2C DRIVER
> >  M:	Haavard Skinnemoen <hskinnemoen@gmail.com>
> >  S:	Supported
> 
> Could you move the MAINTAINERS change to a different patch? It would 
> make it easier to integrate separately.

If needed, yes sure.

> 
> > diff --git a/drivers/gpu/drm/bridge/Kconfig b/drivers/gpu/drm/bridge/Kconfig
> > index b590e67..b4b70fb 100644
> > --- a/drivers/gpu/drm/bridge/Kconfig
> > +++ b/drivers/gpu/drm/bridge/Kconfig
> > @@ -32,6 +32,17 @@ config DRM_DW_HDMI_AHB_AUDIO
> >  	  Designware HDMI block.  This is used in conjunction with
> >  	  the i.MX6 HDMI driver.
> >
> > +config DRM_GE_B850V3_LVDS_DP
> > +	tristate "GE B850v3 LVDS to DP++ display bridge"
> > +	depends on OF
> > +	select DRM_KMS_HELPER
> > +	select DRM_PANEL
> > +	---help---
> > +          This is a driver for the display bridge of
> > +          GE B850v3 that convert dual channel LVDS
> > +          to DP++. This is used with the i.MX6 imx-ldb
> > +          driver.
> > +
> >  config DRM_NXP_PTN3460
> >  	tristate "NXP PTN3460 DP/LVDS bridge"
> >  	depends on OF
> > diff --git a/drivers/gpu/drm/bridge/Makefile b/drivers/gpu/drm/bridge/Makefile
> > index efdb07e..b9606f3 100644
> > --- a/drivers/gpu/drm/bridge/Makefile
> > +++ b/drivers/gpu/drm/bridge/Makefile
> > @@ -3,6 +3,7 @@ ccflags-y := -Iinclude/drm
> >  obj-$(CONFIG_DRM_ANALOGIX_ANX78XX) += analogix-anx78xx.o
> >  obj-$(CONFIG_DRM_DW_HDMI) += dw-hdmi.o
> >  obj-$(CONFIG_DRM_DW_HDMI_AHB_AUDIO) += dw-hdmi-ahb-audio.o
> > +obj-$(CONFIG_DRM_GE_B850V3_LVDS_DP) += ge_b850v3_lvds_dp.o
> >  obj-$(CONFIG_DRM_NXP_PTN3460) += nxp-ptn3460.o
> >  obj-$(CONFIG_DRM_PARADE_PS8622) += parade-ps8622.o
> >  obj-$(CONFIG_DRM_SII902X) += sii902x.o
> > diff --git a/drivers/gpu/drm/bridge/ge_b850v3_lvds_dp.c b/drivers/gpu/drm/bridge/ge_b850v3_lvds_dp.c
> > new file mode 100644
> > index 0000000..81e9279
> > --- /dev/null
> > +++ b/drivers/gpu/drm/bridge/ge_b850v3_lvds_dp.c
> > @@ -0,0 +1,405 @@
> > +/*
> > + * Driver for GE B850v3 DP display bridge
> > +
> > + * Copyright (c) 2016, Collabora Ltd.
> > + * Copyright (c) 2016, General Electric Company
> > +
> > + * This program is free software; you can redistribute it and/or modify it
> > + * under the terms and conditions of the GNU General Public License,
> > + * version 2, as published by the Free Software Foundation.
> > +
> > + * This program is distributed in the hope it will be useful, but WITHOUT
> > + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
> > + * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License for
> > + * more details.
> > +
> > + * You should have received a copy of the GNU General Public License
> > + * along with this program.  If not, see <http://www.gnu.org/licenses/>.
> > +
> > + * This driver creates a drm_bridge and a drm_connector for the LVDS to DP++
> > + * display bridge of the GE B850v3. There are two physical bridges on the video
> > + * signal pipeline: a STDP4028(LVDS to DP) and a STDP2690(DP to DP++). However
> > + * the physical bridges are automatically configured by the input video signal,
> > + * and the driver has no access to the video processing pipeline. The driver is
> > + * only needed to read EDID from the STDP2690 and to handle HPD events from the
> > + * STDP4028. The driver communicates with both bridges over i2c. The video
> > + * signal pipeline is as follows:
> > + *
> > + *   Host -> LVDS|--(STDP4028)--|DP -> DP|--(STDP2690)--|DP++ -> Video output
> > + *
> > + */
> > +
> > +#include <linux/gpio.h>
> > +#include <linux/i2c.h>
> > +#include <linux/module.h>
> > +#include <linux/of.h>
> > +#include <drm/drm_atomic.h>
> > +#include <drm/drm_atomic_helper.h>
> > +#include <drm/drm_crtc_helper.h>
> > +#include <drm/drm_edid.h>
> > +#include <drm/drmP.h>
> > +
> > +/*
> > + * 220Mhz is a limitation of the host, as the bridge is capable of up to
> > + * 330Mhz. See section 9.2.1.2.4 of the i.MX 6Dual/6Quad Applications
> > + * Processor Reference Manual for more information about the 220Mhz limit.
> > + * The imx-ldb driver will warn about clocks over 170Mhz, but it seem to work
> > + * fine.
> > + */
> > +#define MAX_PIXEL_CLOCK 220000
> > +
> > +#define EDID_EXT_BLOCK_CNT 0x7E
> > +
> > +#define STDP4028_IRQ_OUT_CONF_REG 0x02
> > +#define STDP4028_DPTX_IRQ_EN_REG 0x3C
> > +#define STDP4028_DPTX_IRQ_STS_REG 0x3D
> > +#define STDP4028_DPTX_STS_REG 0x3E
> > +
> > +#define STDP4028_DPTX_DP_IRQ_EN 0x1000
> > +
> > +#define STDP4028_DPTX_HOTPLUG_IRQ_EN 0x0400
> > +#define STDP4028_DPTX_LINK_CH_IRQ_EN 0x2000
> > +#define STDP4028_DPTX_IRQ_CONFIG \
> > +		(STDP4028_DPTX_LINK_CH_IRQ_EN | STDP4028_DPTX_HOTPLUG_IRQ_EN)
> > +
> > +#define STDP4028_DPTX_HOTPLUG_STS 0x0200
> > +#define STDP4028_DPTX_LINK_STS 0x1000
> > +#define STDP4028_CON_STATE_CONNECTED \
> > +		(STDP4028_DPTX_HOTPLUG_STS | STDP4028_DPTX_LINK_STS)
> > +
> > +#define STDP4028_DPTX_HOTPLUG_CH_STS 0x0400
> > +#define STDP4028_DPTX_LINK_CH_STS 0x2000
> > +#define STDP4028_DPTX_IRQ_CLEAR \
> > +		(STDP4028_DPTX_LINK_CH_STS | STDP4028_DPTX_HOTPLUG_CH_STS)
> > +
> > +struct ge_b850v3_lvds_dp {
> > +	struct drm_connector connector;
> > +	struct drm_bridge bridge;
> > +	struct i2c_client *ge_b850v3_lvds_dp_i2c;
> > +	struct i2c_client *edid_i2c;
> > +	struct edid *edid;
> > +	struct mutex lock;
> > +};
> > +
> > +static inline struct ge_b850v3_lvds_dp *
> > +		bridge_to_ge_b850v3_lvds_dp(struct drm_bridge *bridge)
> > +{
> > +	return container_of(bridge, struct ge_b850v3_lvds_dp, bridge);
> > +}
> > +
> > +static inline struct ge_b850v3_lvds_dp *
> > +		connector_to_ge_b850v3_lvds_dp(struct drm_connector *connector)
> > +{
> > +	return container_of(connector, struct ge_b850v3_lvds_dp, connector);
> > +}
> > +
> > +u8 *stdp2690_get_edid(struct i2c_client *client)
> > +{
> > +	struct i2c_adapter *adapter = client->adapter;
> > +	unsigned char start = 0x00;
> > +	unsigned int total_size;
> > +	u8 *block = kmalloc(EDID_LENGTH, GFP_KERNEL);
> > +
> > +	struct i2c_msg msgs[] = {
> > +		{
> > +			.addr	= client->addr,
> > +			.flags	= 0,
> > +			.len	= 1,
> > +			.buf	= &start,
> > +		}, {
> > +			.addr	= client->addr,
> > +			.flags	= I2C_M_RD,
> > +			.len	= EDID_LENGTH,
> > +			.buf	= block,
> > +		}
> > +	};
> > +
> > +	if (!block)
> > +		return NULL;
> > +
> > +	if (i2c_transfer(adapter, msgs, 2) != 2) {
> > +		DRM_ERROR("Unable to read EDID.\n");
> > +		goto err;
> > +	}
> > +
> > +	if (!drm_edid_block_valid(block, 0, false, NULL)) {
> > +		DRM_ERROR("Invalid EDID block\n");
> > +		goto err;
> > +	}
> > +
> > +	total_size = (block[EDID_EXT_BLOCK_CNT] + 1) * EDID_LENGTH;
> > +	if (total_size > EDID_LENGTH) {
> > +		kfree(block);
> > +		block = kmalloc(total_size, GFP_KERNEL);
> > +		if (!block)
> > +			return NULL;
> > +
> > +		/* Yes, read the entire buffer, and do not skip the first
> > +		 * EDID_LENGTH bytes.
> > +		 */
> > +		start = 0x00;
> > +		msgs[1].len = total_size;
> > +		msgs[1].buf = block;
> > +
> > +		if (i2c_transfer(adapter, msgs, 2) != 2) {
> > +			DRM_ERROR("Unable to read EDID extension blocks.\n");
> > +			goto err;
> > +		}
> > +	}
> > +
> > +	return block;
> > +
> > +err:
> > +	kfree(block);
> > +	return NULL;
> > +}
> > +
> > +static int ge_b850v3_lvds_dp_get_modes(struct drm_connector *connector)
> > +{
> > +	struct ge_b850v3_lvds_dp *ptn_bridge;
> > +	struct i2c_client *client;
> > +	int num_modes = 0;
> > +
> > +	ptn_bridge = connector_to_ge_b850v3_lvds_dp(connector);
> > +	client = ptn_bridge->edid_i2c;
> > +
> > +	mutex_lock(&ptn_bridge->lock);
> > +
> > +	kfree(ptn_bridge->edid);
> > +	ptn_bridge->edid = (struct edid *) stdp2690_get_edid(client);
> > +
> > +	if (ptn_bridge->edid) {
> > +		drm_mode_connector_update_edid_property(connector,
> > +				ptn_bridge->edid);
> > +		num_modes = drm_add_edid_modes(connector, ptn_bridge->edid);
> > +	}
> > +
> > +	mutex_unlock(&ptn_bridge->lock);
> > +
> > +	return num_modes;
> > +}
> > +
> > +
> > +static enum drm_mode_status ge_b850v3_lvds_dp_mode_valid(
> > +		struct drm_connector *connector, struct drm_display_mode *mode)
> > +{
> > +	if (mode->clock > MAX_PIXEL_CLOCK) {
> > +		DRM_INFO("The pixel clock for the mode %s is too high, and not supported.",
> > +				mode->name);
> > +		return MODE_CLOCK_HIGH;
> > +	}
> > +
> > +	return MODE_OK;
> > +}
> > +
> > +static const struct
> > +drm_connector_helper_funcs ge_b850v3_lvds_dp_connector_helper_funcs = {
> > +	.get_modes = ge_b850v3_lvds_dp_get_modes,
> > +	.mode_valid = ge_b850v3_lvds_dp_mode_valid,
> > +};
> > +
> > +static enum drm_connector_status ge_b850v3_lvds_dp_detect(
> > +		struct drm_connector *connector, bool force)
> > +{
> > +	struct ge_b850v3_lvds_dp *ptn_bridge =
> > +			connector_to_ge_b850v3_lvds_dp(connector);
> > +	struct i2c_client *ge_b850v3_lvds_dp_i2c =
> > +			ptn_bridge->ge_b850v3_lvds_dp_i2c;
> > +	s32 link_state;
> > +
> > +	link_state = i2c_smbus_read_word_data(ge_b850v3_lvds_dp_i2c,
> > +			STDP4028_DPTX_STS_REG);
> > +
> > +	if (link_state == STDP4028_CON_STATE_CONNECTED)
> > +		return connector_status_connected;
> > +
> > +	if (link_state == 0)
> > +		return connector_status_disconnected;
> > +
> > +	return connector_status_unknown;
> > +}
> > +
> > +static const struct drm_connector_funcs ge_b850v3_lvds_dp_connector_funcs = {
> > +	.dpms = drm_atomic_helper_connector_dpms,
> > +	.fill_modes = drm_helper_probe_single_connector_modes,
> > +	.detect = ge_b850v3_lvds_dp_detect,
> > +	.destroy = drm_connector_cleanup,
> > +	.reset = drm_atomic_helper_connector_reset,
> > +	.atomic_duplicate_state = drm_atomic_helper_connector_duplicate_state,
> > +	.atomic_destroy_state = drm_atomic_helper_connector_destroy_state,
> > +};
> > +
> > +static irqreturn_t ge_b850v3_lvds_dp_irq_handler(int irq, void *dev_id)
> > +{
> > +	struct ge_b850v3_lvds_dp *ptn_bridge = dev_id;
> > +	struct i2c_client *ge_b850v3_lvds_dp_i2c
> > +			= ptn_bridge->ge_b850v3_lvds_dp_i2c;
> > +
> > +	mutex_lock(&ptn_bridge->lock);
> > +
> > +	i2c_smbus_write_word_data(ge_b850v3_lvds_dp_i2c,

> > +			STDP4028_DPTX_IRQ_STS_REG, STDP4028_DPTX_IRQ_CLEAR);
> > +
> > +	mutex_unlock(&ptn_bridge->lock);
> > +
> > +	if (ptn_bridge->connector.dev)
> > +		drm_kms_helper_hotplug_event(ptn_bridge->connector.dev);
> > +
> > +	return IRQ_HANDLED;
> > +}
> > +
> > +static int ge_b850v3_lvds_dp_attach(struct drm_bridge *bridge)
> > +{
> > +	struct ge_b850v3_lvds_dp *ptn_bridge
> > +			= bridge_to_ge_b850v3_lvds_dp(bridge);
> > +	struct drm_connector *connector = &ptn_bridge->connector;
> > +	struct i2c_client *ge_b850v3_lvds_dp_i2c
> > +			= ptn_bridge->ge_b850v3_lvds_dp_i2c;
> > +	int ret;
> > +
> > +	if (!bridge->encoder) {
> > +		DRM_ERROR("Parent encoder object not found");
> > +		return -ENODEV;
> > +	}
> > +
> > +	connector->polled = DRM_CONNECTOR_POLL_HPD;
> > +
> > +	drm_connector_helper_add(connector,
> > +			&ge_b850v3_lvds_dp_connector_helper_funcs);
> > +
> > +	ret = drm_connector_init(bridge->dev, connector,
> > +			&ge_b850v3_lvds_dp_connector_funcs,
> > +			DRM_MODE_CONNECTOR_DisplayPort);
> > +	if (ret) {
> > +		DRM_ERROR("Failed to initialize connector with drm\n");
> > +		return ret;
> > +	}
> > +
> > +	drm_connector_register(connector);
> 
> Connectors shouldn't be registered in the bridge driver, they should

> be registered by the kms driver after drm_dev_register is called.
> The drm_connector_register_all() API is used for that.

Hmm, I got this from:

drivers/gpu/drm/bridge/nxp-ptn3460.c:	drm_connector_register(&ptn_bridge->connector);
drivers/gpu/drm/bridge/parade-ps8622.c:	drm_connector_register(&ps8622->connector);
drivers/gpu/drm/bridge/analogix-anx78xx.c:	err = drm_connector_register(&anx78xx->connector);


> 
> > +	ret = drm_mode_connector_attach_encoder(connector, bridge->encoder);
> > +	if (ret)
> > +		return ret;
> > +
> > +	drm_bridge_enable(bridge);
> 
> This drm_bridge_enable() doesn't seem to serve any purpose here. It also
> doesn't seem to make much sense to call drm_bridge_() funcs within a
> bridge op itself.

Yes, removing this line has no effect. I'll send V6. Thank you!

> 
> > +	if (ge_b850v3_lvds_dp_i2c->irq) {
> > +		drm_helper_hpd_irq_event(connector->dev);
> > +
> > +		ret = devm_request_threaded_irq(&ge_b850v3_lvds_dp_i2c->dev,
> > +				ge_b850v3_lvds_dp_i2c->irq, NULL,
> > +				ge_b850v3_lvds_dp_irq_handler,
> > +				IRQF_TRIGGER_HIGH | IRQF_ONESHOT,
> > +				"ge-b850v3-lvds-dp", ptn_bridge);
> 
> Is there a reason why we register the interrupt handler here and not in
> probe?

Yes, drm_bridge_funcs.attach() is called when drm decides it is time to initialize the bridge, which means the drm is already running. i2c_driver.probe() is likely to be called way before drm initializes, and if for some reason the bridge is not attached, there is no need for the interrupt handler. In this case the interrupt handler is mostly used for events related to display plugging/unplugging.

^ permalink raw reply

* [PATCH 5/5] arm64: Add uprobe support
From: Catalin Marinas @ 2016-09-26 11:01 UTC (permalink / raw)
  To: linux-arm-kernel
In-Reply-To: <CAHB_GupuUYPNFoYQeX8EhAcesvqKVVuphuxneudZ5kL2cxm-7g@mail.gmail.com>

On Sun, Sep 25, 2016 at 10:32:28PM +0530, Pratyush Anand wrote:
> On Fri, Sep 23, 2016 at 6:35 PM, Catalin Marinas
> <catalin.marinas@arm.com> wrote:
> > On Fri, Sep 23, 2016 at 09:42:30AM +0530, Pratyush Anand wrote:
> >> On 22/09/2016:05:50:30 PM, Catalin Marinas wrote:
> >> > On Thu, Sep 22, 2016 at 08:53:28AM +0530, Pratyush Anand wrote:
> >> > > On 21/09/2016:06:04:04 PM, Catalin Marinas wrote:
> 
> >> > As a quick workaround you could check mm->task_size > TASK_SIZE_32 in
> >> > the arch_uprobe_analyze_insn() function.
> >>
> >> It would be doable. TASK_SIZE_32 is defined only for COMPAT. So, may be I can
> >> return -EINVAL when mm->task_size < TASK_SIZE_64.
> >
> > That's just a temporary workaround. If we ever merge ILP32, this test
> > would no longer be enough (as the ISA is AArch64 but with TASK_SIZE_32).
> 
> OK.. So what about doing something similar what x86 is doing.
> We can have a flag for task Type in arch specific mm_context_t. We
> also set this flag in COMPAT_SET_PERSONALITY() along with setting
> thread_info flag, and we clear them in SET_PERSONALITY().

This looks like a better approach.

> > Looking at prepare_uprobe(), we have a weak is_trap_insn() function.
> > This check is meaningless without knowing which instruction set we
> > target. A false positive here, however, is not that bad as we wouldn't
> > end up inserting the wrong breakpoint in the executable. But it looks to
> > me like the core uprobe code needs to pass some additional information
> > like the type of task or ELF format to the arch code to make a useful
> > choice of breakpoint type.
> 
> It seems that 'strtle r0, [r0], #160' would have the closest matching
> aarch32 instruction wrt BRK64_OPCODE_UPROBES(0xd42000A0). But that too
> seems a bad instruction. So, may be we can use still weak
> is_trap_insn().

Even if the is_trap_insn() check passes, we would reject the probe in
arch_uprobe_analyze_insn() immediately after based on the mm type check,
so not too bad.

If we add support for probing 32-bit tasks, I would rather have
is_trap_insn() take the mm_struct as argument so that a non-weak
implementation can check for the correct encoding.

-- 
Catalin

^ permalink raw reply

* [PATCH] usb: gadget: udc: atmel: fix endpoint name
From: Richard Genoud @ 2016-09-26 10:47 UTC (permalink / raw)
  To: linux-arm-kernel
In-Reply-To: <874m535dti.fsf@linux.intel.com>

2016-09-26 9:18 GMT+02:00 Felipe Balbi <balbi@kernel.org>:
>
> Hi,
>
> Greg Kroah-Hartman <gregkh@linuxfoundation.org> writes:
>> On Fri, Sep 23, 2016 at 04:20:45PM +0200, Nicolas Ferre wrote:
>>> Le 16/09/2016 ? 10:36, Nicolas Ferre a ?crit :
>>> > Le 15/09/2016 ? 17:07, Alexandre Belloni a ?crit :
>>> >> Since commit c32b5bcfa3c4 ("ARM: dts: at91: Fix USB endpoint nodes"),
>>> >> atmel_usba_udc fails with:
>>> >>
>>> >> ------------[ cut here ]------------
>>> >> WARNING: CPU: 0 PID: 0 at include/linux/usb/gadget.h:405
>>> >> ecm_do_notify+0x188/0x1a0
>>> >> Modules linked in:
>>> >> CPU: 0 PID: 0 Comm: swapper Not tainted 4.7.0+ #15
>>> >> Hardware name: Atmel SAMA5
>>> >> [<c010ccfc>] (unwind_backtrace) from [<c010a7ec>] (show_stack+0x10/0x14)
>>> >> [<c010a7ec>] (show_stack) from [<c0115c10>] (__warn+0xe4/0xfc)
>>> >> [<c0115c10>] (__warn) from [<c0115cd8>] (warn_slowpath_null+0x20/0x28)
>>> >> [<c0115cd8>] (warn_slowpath_null) from [<c04377ac>] (ecm_do_notify+0x188/0x1a0)
>>> >> [<c04377ac>] (ecm_do_notify) from [<c04379a4>] (ecm_set_alt+0x74/0x1ac)
>>> >> [<c04379a4>] (ecm_set_alt) from [<c042f74c>] (composite_setup+0xfc0/0x19f8)
>>> >> [<c042f74c>] (composite_setup) from [<c04356e8>] (usba_udc_irq+0x8f4/0xd9c)
>>> >> [<c04356e8>] (usba_udc_irq) from [<c013ec9c>] (handle_irq_event_percpu+0x9c/0x158)
>>> >> [<c013ec9c>] (handle_irq_event_percpu) from [<c013ed80>] (handle_irq_event+0x28/0x3c)
>>> >> [<c013ed80>] (handle_irq_event) from [<c01416d4>] (handle_fasteoi_irq+0xa0/0x168)
>>> >> [<c01416d4>] (handle_fasteoi_irq) from [<c013e3f8>] (generic_handle_irq+0x24/0x34)
>>> >> [<c013e3f8>] (generic_handle_irq) from [<c013e640>] (__handle_domain_irq+0x54/0xa8)
>>> >> [<c013e640>] (__handle_domain_irq) from [<c010b214>] (__irq_svc+0x54/0x70)
>>> >> [<c010b214>] (__irq_svc) from [<c0107eb0>] (arch_cpu_idle+0x38/0x3c)
>>> >> [<c0107eb0>] (arch_cpu_idle) from [<c0137300>] (cpu_startup_entry+0x9c/0xdc)
>>> >> [<c0137300>] (cpu_startup_entry) from [<c0900c40>] (start_kernel+0x354/0x360)
>>> >> [<c0900c40>] (start_kernel) from [<20008078>] (0x20008078)
>>> >> ---[ end trace e7cf9dcebf4815a6 ]---
>>> >>
>>> >> Fixes: c32b5bcfa3c4 ("ARM: dts: at91: Fix USB endpoint nodes")
>>> >> Reported-by: Richard Genoud <richard.genoud@gmail.com>
>>> >> Signed-off-by: Alexandre Belloni <alexandre.belloni@free-electrons.com>
>>> >
>>> > Acked-by: Nicolas Ferre <nicolas.ferre@atmel.com>
>>> >
>>> > Felipe, Greg,
>>> > It is clearly a regression and material for 4.8-fixes. But I do know
>>> > that we are very late in the process :-(
>>> > Please do what you can to make it progress before 4.8-final but I'm
>>> > truly aware of the challenge.
>>>
>>> Any chance that we can have it (aka ping)?
>>
>> It's Felipe's area, not mine :)
>
> Sorry, I had missed this one. Greg, seems like this would be the only
> pending fix. Do you want it in a pull request or would you prefer to
> just pick it up as a patch? Works either way for me. In case you decide
> to pick it up as a patch:
>
> Acked-by: Felipe Balbi <felipe.balbi@linux.intel.com>
>
> If you prefer to pick it up as a pull request, I already have the patch
> in my 'fixes' branch, just need to tag it and send it to you.
>
> --
> balbi

All seems ok, thanks Alexandre.

Tested-by: Richard Genoud <richard.genoud@gmail.com>

^ permalink raw reply

* [PATCH V5 3/4] drm/bridge: Add driver for GE B850v3 LVDS/DP++ Bridge
From: Archit Taneja @ 2016-09-26 10:29 UTC (permalink / raw)
  To: linux-arm-kernel
In-Reply-To: <37d177d89841be8ff8eec395e3267d1ebf0dfc54.1470739193.git.peter.senna@collabora.com>

Hi,

Some comments.

On 08/09/2016 10:11 PM, Peter Senna Tschudin wrote:
> Add a driver that create a drm_bridge and a drm_connector for the LVDS
> to DP++ display bridge of the GE B850v3.
>
> There are two physical bridges on the video signal pipeline: a
> STDP4028(LVDS to DP) and a STDP2690(DP to DP++).  The hardware and
> firmware made it complicated for this binding to comprise two device
> tree nodes, as the design goal is to configure both bridges based on
> the LVDS signal, which leave the driver powerless to control the video
> processing pipeline. The two bridges behaves as a single bridge, and
> the driver is only needed for telling the host about EDID / HPD, and
> for giving the host powers to ack interrupts. The video signal pipeline
> is as follows:
>
>   Host -> LVDS|--(STDP4028)--|DP -> DP|--(STDP2690)--|DP++ -> Video output
>
> Cc: Martyn Welch <martyn.welch@collabora.co.uk>
> Cc: Martin Donnelly <martin.donnelly@ge.com>
> Cc: Daniel Vetter <daniel.vetter@ffwll.ch>
> Cc: Enric Balletbo i Serra <enric.balletbo@collabora.com>
> Cc: Philipp Zabel <p.zabel@pengutronix.de>
> Cc: Rob Herring <robh@kernel.org>
> Cc: Fabio Estevam <fabio.estevam@nxp.com>
> CC: David Airlie <airlied@linux.ie>
> CC: Thierry Reding <treding@nvidia.com>
> CC: Thierry Reding <thierry.reding@gmail.com>
> Reviewed-by: Enric Balletbo <enric.balletbo@collabora.com>
> Signed-off-by: Peter Senna Tschudin <peter.senna@collabora.com>
> ---
> Changes from V4:
>  - Check the output of the first call to i2c_smbus_write_word_data() and return
>    it's error code for failing gracefully on i2c issues
>  - Renamed the i2c_driver.name from "ge,b850v3-lvds-dp" to "b850v3-lvds-dp" to
>    remove the comma from the driver name
>
> Changes from V3:
>  - 3/4 instead of 4/5
>  - Tested on next-20160804
>
> Changes from V2:
>  - Made it atomic to be applied on next-20160729 on top of Liu Ying changes
>    that made imx-ldb atomic
>
> Changes from V1:
>  - New commit message
>  - Removed 3 empty entry points
>  - Removed memory leak from ge_b850v3_lvds_dp_get_modes()
>  - Added a lock for mode setting
>  - Removed a few blank lines
>  - Changed the order at Makefile and Kconfig
>
>  MAINTAINERS                                |   8 +
>  drivers/gpu/drm/bridge/Kconfig             |  11 +
>  drivers/gpu/drm/bridge/Makefile            |   1 +
>  drivers/gpu/drm/bridge/ge_b850v3_lvds_dp.c | 405 +++++++++++++++++++++++++++++
>  4 files changed, 425 insertions(+)
>  create mode 100644 drivers/gpu/drm/bridge/ge_b850v3_lvds_dp.c
>
> diff --git a/MAINTAINERS b/MAINTAINERS
> index a306795..e8d106a 100644
> --- a/MAINTAINERS
> +++ b/MAINTAINERS
> @@ -5142,6 +5142,14 @@ W:	https://linuxtv.org
>  S:	Maintained
>  F:	drivers/media/radio/radio-gemtek*
>
> +GENERAL ELECTRIC B850V3 LVDS/DP++ BRIDGE
> +M:	Peter Senna Tschudin <peter.senna@collabora.com>
> +M:	Martin Donnelly <martin.donnelly@ge.com>
> +M:	Martyn Welch <martyn.welch@collabora.co.uk>
> +S:	Maintained
> +F:	drivers/gpu/drm/bridge/ge_b850v3_dp2.c
> +F:	Documentation/devicetree/bindings/ge/b850v3_dp2_bridge.txt
> +
>  GENERIC GPIO I2C DRIVER
>  M:	Haavard Skinnemoen <hskinnemoen@gmail.com>
>  S:	Supported

Could you move the MAINTAINERS change to a different patch? It would 
make it easier to integrate separately.

> diff --git a/drivers/gpu/drm/bridge/Kconfig b/drivers/gpu/drm/bridge/Kconfig
> index b590e67..b4b70fb 100644
> --- a/drivers/gpu/drm/bridge/Kconfig
> +++ b/drivers/gpu/drm/bridge/Kconfig
> @@ -32,6 +32,17 @@ config DRM_DW_HDMI_AHB_AUDIO
>  	  Designware HDMI block.  This is used in conjunction with
>  	  the i.MX6 HDMI driver.
>
> +config DRM_GE_B850V3_LVDS_DP
> +	tristate "GE B850v3 LVDS to DP++ display bridge"
> +	depends on OF
> +	select DRM_KMS_HELPER
> +	select DRM_PANEL
> +	---help---
> +          This is a driver for the display bridge of
> +          GE B850v3 that convert dual channel LVDS
> +          to DP++. This is used with the i.MX6 imx-ldb
> +          driver.
> +
>  config DRM_NXP_PTN3460
>  	tristate "NXP PTN3460 DP/LVDS bridge"
>  	depends on OF
> diff --git a/drivers/gpu/drm/bridge/Makefile b/drivers/gpu/drm/bridge/Makefile
> index efdb07e..b9606f3 100644
> --- a/drivers/gpu/drm/bridge/Makefile
> +++ b/drivers/gpu/drm/bridge/Makefile
> @@ -3,6 +3,7 @@ ccflags-y := -Iinclude/drm
>  obj-$(CONFIG_DRM_ANALOGIX_ANX78XX) += analogix-anx78xx.o
>  obj-$(CONFIG_DRM_DW_HDMI) += dw-hdmi.o
>  obj-$(CONFIG_DRM_DW_HDMI_AHB_AUDIO) += dw-hdmi-ahb-audio.o
> +obj-$(CONFIG_DRM_GE_B850V3_LVDS_DP) += ge_b850v3_lvds_dp.o
>  obj-$(CONFIG_DRM_NXP_PTN3460) += nxp-ptn3460.o
>  obj-$(CONFIG_DRM_PARADE_PS8622) += parade-ps8622.o
>  obj-$(CONFIG_DRM_SII902X) += sii902x.o
> diff --git a/drivers/gpu/drm/bridge/ge_b850v3_lvds_dp.c b/drivers/gpu/drm/bridge/ge_b850v3_lvds_dp.c
> new file mode 100644
> index 0000000..81e9279
> --- /dev/null
> +++ b/drivers/gpu/drm/bridge/ge_b850v3_lvds_dp.c
> @@ -0,0 +1,405 @@
> +/*
> + * Driver for GE B850v3 DP display bridge
> +
> + * Copyright (c) 2016, Collabora Ltd.
> + * Copyright (c) 2016, General Electric Company
> +
> + * This program is free software; you can redistribute it and/or modify it
> + * under the terms and conditions of the GNU General Public License,
> + * version 2, as published by the Free Software Foundation.
> +
> + * This program is distributed in the hope it will be useful, but WITHOUT
> + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
> + * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License for
> + * more details.
> +
> + * You should have received a copy of the GNU General Public License
> + * along with this program.  If not, see <http://www.gnu.org/licenses/>.
> +
> + * This driver creates a drm_bridge and a drm_connector for the LVDS to DP++
> + * display bridge of the GE B850v3. There are two physical bridges on the video
> + * signal pipeline: a STDP4028(LVDS to DP) and a STDP2690(DP to DP++). However
> + * the physical bridges are automatically configured by the input video signal,
> + * and the driver has no access to the video processing pipeline. The driver is
> + * only needed to read EDID from the STDP2690 and to handle HPD events from the
> + * STDP4028. The driver communicates with both bridges over i2c. The video
> + * signal pipeline is as follows:
> + *
> + *   Host -> LVDS|--(STDP4028)--|DP -> DP|--(STDP2690)--|DP++ -> Video output
> + *
> + */
> +
> +#include <linux/gpio.h>
> +#include <linux/i2c.h>
> +#include <linux/module.h>
> +#include <linux/of.h>
> +#include <drm/drm_atomic.h>
> +#include <drm/drm_atomic_helper.h>
> +#include <drm/drm_crtc_helper.h>
> +#include <drm/drm_edid.h>
> +#include <drm/drmP.h>
> +
> +/*
> + * 220Mhz is a limitation of the host, as the bridge is capable of up to
> + * 330Mhz. See section 9.2.1.2.4 of the i.MX 6Dual/6Quad Applications
> + * Processor Reference Manual for more information about the 220Mhz limit.
> + * The imx-ldb driver will warn about clocks over 170Mhz, but it seem to work
> + * fine.
> + */
> +#define MAX_PIXEL_CLOCK 220000
> +
> +#define EDID_EXT_BLOCK_CNT 0x7E
> +
> +#define STDP4028_IRQ_OUT_CONF_REG 0x02
> +#define STDP4028_DPTX_IRQ_EN_REG 0x3C
> +#define STDP4028_DPTX_IRQ_STS_REG 0x3D
> +#define STDP4028_DPTX_STS_REG 0x3E
> +
> +#define STDP4028_DPTX_DP_IRQ_EN 0x1000
> +
> +#define STDP4028_DPTX_HOTPLUG_IRQ_EN 0x0400
> +#define STDP4028_DPTX_LINK_CH_IRQ_EN 0x2000
> +#define STDP4028_DPTX_IRQ_CONFIG \
> +		(STDP4028_DPTX_LINK_CH_IRQ_EN | STDP4028_DPTX_HOTPLUG_IRQ_EN)
> +
> +#define STDP4028_DPTX_HOTPLUG_STS 0x0200
> +#define STDP4028_DPTX_LINK_STS 0x1000
> +#define STDP4028_CON_STATE_CONNECTED \
> +		(STDP4028_DPTX_HOTPLUG_STS | STDP4028_DPTX_LINK_STS)
> +
> +#define STDP4028_DPTX_HOTPLUG_CH_STS 0x0400
> +#define STDP4028_DPTX_LINK_CH_STS 0x2000
> +#define STDP4028_DPTX_IRQ_CLEAR \
> +		(STDP4028_DPTX_LINK_CH_STS | STDP4028_DPTX_HOTPLUG_CH_STS)
> +
> +struct ge_b850v3_lvds_dp {
> +	struct drm_connector connector;
> +	struct drm_bridge bridge;
> +	struct i2c_client *ge_b850v3_lvds_dp_i2c;
> +	struct i2c_client *edid_i2c;
> +	struct edid *edid;
> +	struct mutex lock;
> +};
> +
> +static inline struct ge_b850v3_lvds_dp *
> +		bridge_to_ge_b850v3_lvds_dp(struct drm_bridge *bridge)
> +{
> +	return container_of(bridge, struct ge_b850v3_lvds_dp, bridge);
> +}
> +
> +static inline struct ge_b850v3_lvds_dp *
> +		connector_to_ge_b850v3_lvds_dp(struct drm_connector *connector)
> +{
> +	return container_of(connector, struct ge_b850v3_lvds_dp, connector);
> +}
> +
> +u8 *stdp2690_get_edid(struct i2c_client *client)
> +{
> +	struct i2c_adapter *adapter = client->adapter;
> +	unsigned char start = 0x00;
> +	unsigned int total_size;
> +	u8 *block = kmalloc(EDID_LENGTH, GFP_KERNEL);
> +
> +	struct i2c_msg msgs[] = {
> +		{
> +			.addr	= client->addr,
> +			.flags	= 0,
> +			.len	= 1,
> +			.buf	= &start,
> +		}, {
> +			.addr	= client->addr,
> +			.flags	= I2C_M_RD,
> +			.len	= EDID_LENGTH,
> +			.buf	= block,
> +		}
> +	};
> +
> +	if (!block)
> +		return NULL;
> +
> +	if (i2c_transfer(adapter, msgs, 2) != 2) {
> +		DRM_ERROR("Unable to read EDID.\n");
> +		goto err;
> +	}
> +
> +	if (!drm_edid_block_valid(block, 0, false, NULL)) {
> +		DRM_ERROR("Invalid EDID block\n");
> +		goto err;
> +	}
> +
> +	total_size = (block[EDID_EXT_BLOCK_CNT] + 1) * EDID_LENGTH;
> +	if (total_size > EDID_LENGTH) {
> +		kfree(block);
> +		block = kmalloc(total_size, GFP_KERNEL);
> +		if (!block)
> +			return NULL;
> +
> +		/* Yes, read the entire buffer, and do not skip the first
> +		 * EDID_LENGTH bytes.
> +		 */
> +		start = 0x00;
> +		msgs[1].len = total_size;
> +		msgs[1].buf = block;
> +
> +		if (i2c_transfer(adapter, msgs, 2) != 2) {
> +			DRM_ERROR("Unable to read EDID extension blocks.\n");
> +			goto err;
> +		}
> +	}
> +
> +	return block;
> +
> +err:
> +	kfree(block);
> +	return NULL;
> +}
> +
> +static int ge_b850v3_lvds_dp_get_modes(struct drm_connector *connector)
> +{
> +	struct ge_b850v3_lvds_dp *ptn_bridge;
> +	struct i2c_client *client;
> +	int num_modes = 0;
> +
> +	ptn_bridge = connector_to_ge_b850v3_lvds_dp(connector);
> +	client = ptn_bridge->edid_i2c;
> +
> +	mutex_lock(&ptn_bridge->lock);
> +
> +	kfree(ptn_bridge->edid);
> +	ptn_bridge->edid = (struct edid *) stdp2690_get_edid(client);
> +
> +	if (ptn_bridge->edid) {
> +		drm_mode_connector_update_edid_property(connector,
> +				ptn_bridge->edid);
> +		num_modes = drm_add_edid_modes(connector, ptn_bridge->edid);
> +	}
> +
> +	mutex_unlock(&ptn_bridge->lock);
> +
> +	return num_modes;
> +}
> +
> +
> +static enum drm_mode_status ge_b850v3_lvds_dp_mode_valid(
> +		struct drm_connector *connector, struct drm_display_mode *mode)
> +{
> +	if (mode->clock > MAX_PIXEL_CLOCK) {
> +		DRM_INFO("The pixel clock for the mode %s is too high, and not supported.",
> +				mode->name);
> +		return MODE_CLOCK_HIGH;
> +	}
> +
> +	return MODE_OK;
> +}
> +
> +static const struct
> +drm_connector_helper_funcs ge_b850v3_lvds_dp_connector_helper_funcs = {
> +	.get_modes = ge_b850v3_lvds_dp_get_modes,
> +	.mode_valid = ge_b850v3_lvds_dp_mode_valid,
> +};
> +
> +static enum drm_connector_status ge_b850v3_lvds_dp_detect(
> +		struct drm_connector *connector, bool force)
> +{
> +	struct ge_b850v3_lvds_dp *ptn_bridge =
> +			connector_to_ge_b850v3_lvds_dp(connector);
> +	struct i2c_client *ge_b850v3_lvds_dp_i2c =
> +			ptn_bridge->ge_b850v3_lvds_dp_i2c;
> +	s32 link_state;
> +
> +	link_state = i2c_smbus_read_word_data(ge_b850v3_lvds_dp_i2c,
> +			STDP4028_DPTX_STS_REG);
> +
> +	if (link_state == STDP4028_CON_STATE_CONNECTED)
> +		return connector_status_connected;
> +
> +	if (link_state == 0)
> +		return connector_status_disconnected;
> +
> +	return connector_status_unknown;
> +}
> +
> +static const struct drm_connector_funcs ge_b850v3_lvds_dp_connector_funcs = {
> +	.dpms = drm_atomic_helper_connector_dpms,
> +	.fill_modes = drm_helper_probe_single_connector_modes,
> +	.detect = ge_b850v3_lvds_dp_detect,
> +	.destroy = drm_connector_cleanup,
> +	.reset = drm_atomic_helper_connector_reset,
> +	.atomic_duplicate_state = drm_atomic_helper_connector_duplicate_state,
> +	.atomic_destroy_state = drm_atomic_helper_connector_destroy_state,
> +};
> +
> +static irqreturn_t ge_b850v3_lvds_dp_irq_handler(int irq, void *dev_id)
> +{
> +	struct ge_b850v3_lvds_dp *ptn_bridge = dev_id;
> +	struct i2c_client *ge_b850v3_lvds_dp_i2c
> +			= ptn_bridge->ge_b850v3_lvds_dp_i2c;
> +
> +	mutex_lock(&ptn_bridge->lock);
> +
> +	i2c_smbus_write_word_data(ge_b850v3_lvds_dp_i2c,
> +			STDP4028_DPTX_IRQ_STS_REG, STDP4028_DPTX_IRQ_CLEAR);
> +
> +	mutex_unlock(&ptn_bridge->lock);
> +
> +	if (ptn_bridge->connector.dev)
> +		drm_kms_helper_hotplug_event(ptn_bridge->connector.dev);
> +
> +	return IRQ_HANDLED;
> +}
> +
> +static int ge_b850v3_lvds_dp_attach(struct drm_bridge *bridge)
> +{
> +	struct ge_b850v3_lvds_dp *ptn_bridge
> +			= bridge_to_ge_b850v3_lvds_dp(bridge);
> +	struct drm_connector *connector = &ptn_bridge->connector;
> +	struct i2c_client *ge_b850v3_lvds_dp_i2c
> +			= ptn_bridge->ge_b850v3_lvds_dp_i2c;
> +	int ret;
> +
> +	if (!bridge->encoder) {
> +		DRM_ERROR("Parent encoder object not found");
> +		return -ENODEV;
> +	}
> +
> +	connector->polled = DRM_CONNECTOR_POLL_HPD;
> +
> +	drm_connector_helper_add(connector,
> +			&ge_b850v3_lvds_dp_connector_helper_funcs);
> +
> +	ret = drm_connector_init(bridge->dev, connector,
> +			&ge_b850v3_lvds_dp_connector_funcs,
> +			DRM_MODE_CONNECTOR_DisplayPort);
> +	if (ret) {
> +		DRM_ERROR("Failed to initialize connector with drm\n");
> +		return ret;
> +	}
> +
> +	drm_connector_register(connector);

Connectors shouldn't be registered in the bridge driver, they should
be registered by the kms driver after drm_dev_register is called.
The drm_connector_register_all() API is used for that.

> +	ret = drm_mode_connector_attach_encoder(connector, bridge->encoder);
> +	if (ret)
> +		return ret;
> +
> +	drm_bridge_enable(bridge);

This drm_bridge_enable() doesn't seem to serve any purpose here. It also
doesn't seem to make much sense to call drm_bridge_() funcs within a
bridge op itself.

> +	if (ge_b850v3_lvds_dp_i2c->irq) {
> +		drm_helper_hpd_irq_event(connector->dev);
> +
> +		ret = devm_request_threaded_irq(&ge_b850v3_lvds_dp_i2c->dev,
> +				ge_b850v3_lvds_dp_i2c->irq, NULL,
> +				ge_b850v3_lvds_dp_irq_handler,
> +				IRQF_TRIGGER_HIGH | IRQF_ONESHOT,
> +				"ge-b850v3-lvds-dp", ptn_bridge);

Is there a reason why we register the interrupt handler here and not in
probe?

Thanks,
Archit

-- 
Qualcomm Innovation Center, Inc. is a member of Code Aurora Forum,
a Linux Foundation Collaborative Project

^ permalink raw reply

* [PATCH V5 3/4] drm/bridge: Add driver for GE B850v3 LVDS/DP++ Bridge
From: Archit Taneja @ 2016-09-26 10:28 UTC (permalink / raw)
  To: linux-arm-kernel
In-Reply-To: <3a56-57e8e300-3-10c8a340@87357206>

Hi,

On 09/26/2016 02:28 PM, Peter Senna Tschudin wrote:
>  Hi Archit,
>
> On Monday, September 26, 2016 10:31 CEST, Archit Taneja <architt@codeaurora.org> wrote:
>
>> Hi Peter,
>>
>> On 09/26/2016 01:57 PM, Peter Senna Tschudin wrote:
>>> Patch 1/4 is already on linux-next, but what about this one? Ping?
>
>>
>> I'd posted some queries a couple of times which you didn't answer to.
>> Could you please respond to them before we try to get this merged?
>
> Your queries were already answered by similar questions. The commit messages and cover letter also addresses the design decisions of the code. But basically the driver usefulness to other scenarios is severely limited by the firmware used by both chips. And when using the firmware that goes with this specific hardware, then yes, the two chips are always expected to work together. But the main point is that with the custom firmware each chip do not behave as independent bridges anymore.

Thanks for the reply.

It wasn't entirely clear from the commit message that a custom firmware
was exclusively used on this board to program these chips in order to
get these 2 working together.

I browsed the earlier versions of the patch and saw you explained
the same thing to someone else. Sorry about that, I missed reading
that before.

Could you please specify this explicitly in the commit message? Perhaps,
also mention that there is an external microcontroller with a custom
firmware that manages most of the video operations. For the sake of
completeness, could you also mention the part name of the controller
that's running this firmware?

Also, in the comments in the beginning of the driver:

"However the physical bridges are automatically configured by the input 
video signal, and the driver has no access to the video processing 
pipeline."

Is the automatic configuration done by the firmware, or is it a
feature of the chips itself?

>
> On the other side, I was careful to use meaningful names for the registers, so a future implementation based on same chips can take the basics from this work, at least as a starting point.

Thanks, that would be handy for later.

I had some comments on the code. I'll share those in another reply.

Archit

>
> Thanks,
>
> Peter
>

-- 
Qualcomm Innovation Center, Inc. is a member of Code Aurora Forum,
a Linux Foundation Collaborative Project

^ permalink raw reply

* [PATCH][v2] drm/sun4i: rgb: Enable panel after controller
From: Jonathan Liu @ 2016-09-26 10:21 UTC (permalink / raw)
  To: linux-arm-kernel

The panel should be enabled after the controller so that we do not have
visual glitches on the panel while the controller is setup. Similarly,
the panel should be disabled before the controller.

Signed-off-by: Jonathan Liu <net147@gmail.com>
---
Changes in v2:
 - Changed the commit message to be clearer

 drivers/gpu/drm/sun4i/sun4i_rgb.c | 14 ++++++++------
 1 file changed, 8 insertions(+), 6 deletions(-)

diff --git a/drivers/gpu/drm/sun4i/sun4i_rgb.c b/drivers/gpu/drm/sun4i/sun4i_rgb.c
index c3ff10f..4e4bea6 100644
--- a/drivers/gpu/drm/sun4i/sun4i_rgb.c
+++ b/drivers/gpu/drm/sun4i/sun4i_rgb.c
@@ -152,15 +152,16 @@ static void sun4i_rgb_encoder_enable(struct drm_encoder *encoder)
 
 	DRM_DEBUG_DRIVER("Enabling RGB output\n");
 
-	if (!IS_ERR(tcon->panel)) {
+	if (!IS_ERR(tcon->panel))
 		drm_panel_prepare(tcon->panel);
-		drm_panel_enable(tcon->panel);
-	}
 
 	/* encoder->bridge can be NULL; drm_bridge_enable checks for it */
 	drm_bridge_enable(encoder->bridge);
 
 	sun4i_tcon_channel_enable(tcon, 0);
+
+	if (!IS_ERR(tcon->panel))
+		drm_panel_enable(tcon->panel);
 }
 
 static void sun4i_rgb_encoder_disable(struct drm_encoder *encoder)
@@ -171,15 +172,16 @@ static void sun4i_rgb_encoder_disable(struct drm_encoder *encoder)
 
 	DRM_DEBUG_DRIVER("Disabling RGB output\n");
 
+	if (!IS_ERR(tcon->panel))
+		drm_panel_disable(tcon->panel);
+
 	sun4i_tcon_channel_disable(tcon, 0);
 
 	/* encoder->bridge can be NULL; drm_bridge_disable checks for it */
 	drm_bridge_disable(encoder->bridge);
 
-	if (!IS_ERR(tcon->panel)) {
-		drm_panel_disable(tcon->panel);
+	if (!IS_ERR(tcon->panel))
 		drm_panel_unprepare(tcon->panel);
-	}
 }
 
 static void sun4i_rgb_encoder_mode_set(struct drm_encoder *encoder,
-- 
2.10.0

^ permalink raw reply related

* [PATCH 1/2] armv8: aarch32: Execute 32-bit Linux for LayerScape platforms
From: Jason Jin @ 2016-09-26  9:58 UTC (permalink / raw)
  To: linux-arm-kernel
In-Reply-To: <4642389.nHAzKPNi96@wuerfel>

> Subject: Re: [PATCH 1/2] armv8: aarch32: Execute 32-bit Linux for LayerScape
> platforms
> 
> On Friday, September 23, 2016 4:09:29 PM CEST Stuart Yoder wrote:
> > > -----Original Message-----
> > > From: Arnd Bergmann [mailto:arnd at arndb.de]
> > > Sent: Friday, September 23, 2016 10:58 AM
> 
> > > > Really, though, the firmware call thing is an incredibly niche use-case.
> > > > Beyond development, the only real benefit of starting an AArch32
> > > > kernel in Hyp is that you can run AArch32 KVM guests, which you
> > > > can do equally well (if not better) under an AArch64 kernel.
> > >
> > > This was my question earlier in the thread, apparently Alison has
> > > another use case in mind, but I don't yet know what that is. If that
> > > use case is important enough, we could do it this way.
> > >
> > > The only use case I can think of at the moment is boot testing on
> > > kernelci.org, which could be used to check whether all the drivers
> > > work in 32-bit environments.
> >
> > The reason we want aarch32 kernel support is for specific customers
> > that have requirements for legacy 32-bit kernel drivers that function
> > on ARMv7 but for some reason are very problematic to port to a 64-bit
> > kernel.  So, the way to ease the 64-bit transition is allow them to run an
> aarch32 kernel
> > and their driver on an ARMv8 SoC.   I don't think we specifically care
> > whether the kernel starts at EL2 or EL1.
> 
> Thanks for the explanation. Unfortunately I don't think that helps get this
> approach upstream, in particular I wouldn't want to add a platform specific
> hack just to help a third party not have to fix their out-of-tree drivers to be 64-
> bit clean.
> 
> If your customer already has a chunk of downstream patches large enough to
> be problematic to rebuild for 64-bit, they should be able to also pick up these
> two patches, but of course what we really want is to get drivers upstream and
> fixed (presumably this would be an even larger task than just fixing them, so I
> don't set my hopes too high here).
> 
> I'm still not opposed to making *all* 64-bit platforms available to 32-bit kernels
> for testing purposes though, and that would solve the customer problem too,
> as would the approach of encapsulating the kernel in hypervisor stub that
> implements the PSCI calls and using MACH_VIRT with the respective layerscape
> drivers enabled.
> 
[Jason] Thanks for all the comments regarding to the ARMv8 32bit kernel patches.
 I'd like to provide some comment from this thread.

To run the 32bit kernel on ARMv8, we also provided some patches to u-boot. The
same U-boot image will be used to boot up the 64bit and 32bit kernel. With u-boot
patches, bootm will automatically identify the kernel image, if it's 32bit kernel image,
u-boot will change the ES to AArch32 EL2 and boot up the 32bit kernel. So we do
not need to wrap the 32bit image with 64bit bootup protocol.

Our FW set the u-boot to EL2 and did not have the 'NEW' SMC call to switch the 
AArch64(EL2) to AArch32(EL2) at this moment, so this patch set did not rely on
the FW and did not  implement the PSCI for secondary core bootup.

I agree with Robin that maybe it's not necessary to support the Hyp for ARMv8 AArch32,
so we can boot up the 32bit kernel with EL1 directly. With this assumption, I think we
can switch to FW and PSCI for SMP easily and drop the spin table patches.

Thanks all for the suggestion. We'll try the "Generic DT based system" instead of
introducing a new platform to support the 32bit kernel. And rely on the dtb built
from ARM64 to bootup the system. Not sure at this moment if some minor update
patches still needed for ARMv8.

Jason
> 	Arnd

^ permalink raw reply

* [PATCH v8 1/4] soc: mediatek: Refine scpsys to support multiple platform
From: James Liao @ 2016-09-26  9:33 UTC (permalink / raw)
  To: linux-arm-kernel
In-Reply-To: <92a5bc68-0c4c-b9e1-17d7-34c83020aa37@gmail.com>

On Wed, 2016-09-21 at 12:25 +0200, Matthias Brugger wrote:
> 
> On 19/07/16 09:04, James Liao wrote:
> > Refine scpsys driver common code to support multiple SoC / platform.
> >
> > Signed-off-by: James Liao <jamesjj.liao@mediatek.com>
> > Reviewed-by: Kevin Hilman <khilman@baylibre.com>
> > ---
> >  drivers/soc/mediatek/mtk-scpsys.c | 363 +++++++++++++++++++++++---------------
> >  1 file changed, 220 insertions(+), 143 deletions(-)
> >
> > diff --git a/drivers/soc/mediatek/mtk-scpsys.c b/drivers/soc/mediatek/mtk-scpsys.c
> > index 837effe..1f3555a 100644
> > --- a/drivers/soc/mediatek/mtk-scpsys.c
> > +++ b/drivers/soc/mediatek/mtk-scpsys.c
> > @@ -11,17 +11,15 @@
> >   * GNU General Public License for more details.
> >   */
> >  #include <linux/clk.h>
> > -#include <linux/delay.h>
> > +#include <linux/init.h>
> >  #include <linux/io.h>
> > -#include <linux/kernel.h>
> >  #include <linux/mfd/syscon.h>
> > -#include <linux/init.h>
> >  #include <linux/of_device.h>
> >  #include <linux/platform_device.h>
> >  #include <linux/pm_domain.h>
> > -#include <linux/regmap.h>
> > -#include <linux/soc/mediatek/infracfg.h>
> >  #include <linux/regulator/consumer.h>
> > +#include <linux/soc/mediatek/infracfg.h>
> > +
> >  #include <dt-bindings/power/mt8173-power.h>
> >
> >  #define SPM_VDE_PWR_CON			0x0210
> > @@ -34,6 +32,7 @@
> >  #define SPM_MFG_2D_PWR_CON		0x02c0
> >  #define SPM_MFG_ASYNC_PWR_CON		0x02c4
> >  #define SPM_USB_PWR_CON			0x02cc
> > +
> >  #define SPM_PWR_STATUS			0x060c
> >  #define SPM_PWR_STATUS_2ND		0x0610
> >
> > @@ -55,12 +54,12 @@
> >  #define PWR_STATUS_USB			BIT(25)
> >
> >  enum clk_id {
> > -	MT8173_CLK_NONE,
> > -	MT8173_CLK_MM,
> > -	MT8173_CLK_MFG,
> > -	MT8173_CLK_VENC,
> > -	MT8173_CLK_VENC_LT,
> > -	MT8173_CLK_MAX,
> > +	CLK_NONE,
> > +	CLK_MM,
> > +	CLK_MFG,
> > +	CLK_VENC,
> > +	CLK_VENC_LT,
> > +	CLK_MAX,
> >  };
> >
> >  #define MAX_CLKS	2
> > @@ -76,98 +75,6 @@ struct scp_domain_data {
> >  	bool active_wakeup;
> >  };
> >
> > -static const struct scp_domain_data scp_domain_data[] = {
> > -	[MT8173_POWER_DOMAIN_VDEC] = {
> > -		.name = "vdec",
> > -		.sta_mask = PWR_STATUS_VDEC,
> > -		.ctl_offs = SPM_VDE_PWR_CON,
> > -		.sram_pdn_bits = GENMASK(11, 8),
> > -		.sram_pdn_ack_bits = GENMASK(12, 12),
> > -		.clk_id = {MT8173_CLK_MM},
> > -	},
> > -	[MT8173_POWER_DOMAIN_VENC] = {
> > -		.name = "venc",
> > -		.sta_mask = PWR_STATUS_VENC,
> > -		.ctl_offs = SPM_VEN_PWR_CON,
> > -		.sram_pdn_bits = GENMASK(11, 8),
> > -		.sram_pdn_ack_bits = GENMASK(15, 12),
> > -		.clk_id = {MT8173_CLK_MM, MT8173_CLK_VENC},
> > -	},
> > -	[MT8173_POWER_DOMAIN_ISP] = {
> > -		.name = "isp",
> > -		.sta_mask = PWR_STATUS_ISP,
> > -		.ctl_offs = SPM_ISP_PWR_CON,
> > -		.sram_pdn_bits = GENMASK(11, 8),
> > -		.sram_pdn_ack_bits = GENMASK(13, 12),
> > -		.clk_id = {MT8173_CLK_MM},
> > -	},
> > -	[MT8173_POWER_DOMAIN_MM] = {
> > -		.name = "mm",
> > -		.sta_mask = PWR_STATUS_DISP,
> > -		.ctl_offs = SPM_DIS_PWR_CON,
> > -		.sram_pdn_bits = GENMASK(11, 8),
> > -		.sram_pdn_ack_bits = GENMASK(12, 12),
> > -		.clk_id = {MT8173_CLK_MM},
> > -		.bus_prot_mask = MT8173_TOP_AXI_PROT_EN_MM_M0 |
> > -			MT8173_TOP_AXI_PROT_EN_MM_M1,
> > -	},
> > -	[MT8173_POWER_DOMAIN_VENC_LT] = {
> > -		.name = "venc_lt",
> > -		.sta_mask = PWR_STATUS_VENC_LT,
> > -		.ctl_offs = SPM_VEN2_PWR_CON,
> > -		.sram_pdn_bits = GENMASK(11, 8),
> > -		.sram_pdn_ack_bits = GENMASK(15, 12),
> > -		.clk_id = {MT8173_CLK_MM, MT8173_CLK_VENC_LT},
> > -	},
> > -	[MT8173_POWER_DOMAIN_AUDIO] = {
> > -		.name = "audio",
> > -		.sta_mask = PWR_STATUS_AUDIO,
> > -		.ctl_offs = SPM_AUDIO_PWR_CON,
> > -		.sram_pdn_bits = GENMASK(11, 8),
> > -		.sram_pdn_ack_bits = GENMASK(15, 12),
> > -		.clk_id = {MT8173_CLK_NONE},
> > -	},
> > -	[MT8173_POWER_DOMAIN_USB] = {
> > -		.name = "usb",
> > -		.sta_mask = PWR_STATUS_USB,
> > -		.ctl_offs = SPM_USB_PWR_CON,
> > -		.sram_pdn_bits = GENMASK(11, 8),
> > -		.sram_pdn_ack_bits = GENMASK(15, 12),
> > -		.clk_id = {MT8173_CLK_NONE},
> > -		.active_wakeup = true,
> > -	},
> > -	[MT8173_POWER_DOMAIN_MFG_ASYNC] = {
> > -		.name = "mfg_async",
> > -		.sta_mask = PWR_STATUS_MFG_ASYNC,
> > -		.ctl_offs = SPM_MFG_ASYNC_PWR_CON,
> > -		.sram_pdn_bits = GENMASK(11, 8),
> > -		.sram_pdn_ack_bits = 0,
> > -		.clk_id = {MT8173_CLK_MFG},
> > -	},
> > -	[MT8173_POWER_DOMAIN_MFG_2D] = {
> > -		.name = "mfg_2d",
> > -		.sta_mask = PWR_STATUS_MFG_2D,
> > -		.ctl_offs = SPM_MFG_2D_PWR_CON,
> > -		.sram_pdn_bits = GENMASK(11, 8),
> > -		.sram_pdn_ack_bits = GENMASK(13, 12),
> > -		.clk_id = {MT8173_CLK_NONE},
> > -	},
> > -	[MT8173_POWER_DOMAIN_MFG] = {
> > -		.name = "mfg",
> > -		.sta_mask = PWR_STATUS_MFG,
> > -		.ctl_offs = SPM_MFG_PWR_CON,
> > -		.sram_pdn_bits = GENMASK(13, 8),
> > -		.sram_pdn_ack_bits = GENMASK(21, 16),
> > -		.clk_id = {MT8173_CLK_NONE},
> > -		.bus_prot_mask = MT8173_TOP_AXI_PROT_EN_MFG_S |
> > -			MT8173_TOP_AXI_PROT_EN_MFG_M0 |
> > -			MT8173_TOP_AXI_PROT_EN_MFG_M1 |
> > -			MT8173_TOP_AXI_PROT_EN_MFG_SNOOP_OUT,
> > -	},
> > -};
> > -
> > -#define NUM_DOMAINS	ARRAY_SIZE(scp_domain_data)
> > -
> >  struct scp;
> >
> >  struct scp_domain {
> > @@ -179,7 +86,7 @@ struct scp_domain {
> >  };
> >
> >  struct scp {
> > -	struct scp_domain domains[NUM_DOMAINS];
> > +	struct scp_domain *domains;
> >  	struct genpd_onecell_data pd_data;
> >  	struct device *dev;
> >  	void __iomem *base;
> > @@ -408,57 +315,69 @@ static bool scpsys_active_wakeup(struct device *dev)
> >  	return scpd->data->active_wakeup;
> >  }
> >
> > -static int scpsys_probe(struct platform_device *pdev)
> > +static void init_clks(struct platform_device *pdev, struct clk *clk[CLK_MAX])
> > +{
> > +	enum clk_id clk_ids[] = {
> > +		CLK_MM,
> > +		CLK_MFG,
> > +		CLK_VENC,
> > +		CLK_VENC_LT
> > +	};
> > +
> > +	static const char * const clk_names[] = {
> > +		"mm",
> > +		"mfg",
> > +		"venc",
> > +		"venc_lt",
> > +	};
> > +
> > +	int i;
> > +
> > +	for (i = 0; i < ARRAY_SIZE(clk_ids); i++)
> > +		clk[clk_ids[i]] = devm_clk_get(&pdev->dev, clk_names[i]);
> 
> Should be:
> for (i = CLK_NONE + 1; i < CLK_MAX; i++)
> 
> then we can get rid of clk_ids[].

I'll change it in next patch.

> > +}
> > +
> > +static struct scp *init_scp(struct platform_device *pdev,
> > +			const struct scp_domain_data *scp_domain_data, int num)
> >  {
> >  	struct genpd_onecell_data *pd_data;
> >  	struct resource *res;
> > -	int i, j, ret;
> > +	int i, j;
> >  	struct scp *scp;
> > -	struct clk *clk[MT8173_CLK_MAX];
> > +	struct clk *clk[CLK_MAX];
> >
> >  	scp = devm_kzalloc(&pdev->dev, sizeof(*scp), GFP_KERNEL);
> >  	if (!scp)
> > -		return -ENOMEM;
> > +		return ERR_PTR(-ENOMEM);
> >
> >  	scp->dev = &pdev->dev;
> >
> >  	res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
> >  	scp->base = devm_ioremap_resource(&pdev->dev, res);
> >  	if (IS_ERR(scp->base))
> > -		return PTR_ERR(scp->base);
> > -
> > -	pd_data = &scp->pd_data;
> > -
> > -	pd_data->domains = devm_kzalloc(&pdev->dev,
> > -			sizeof(*pd_data->domains) * NUM_DOMAINS, GFP_KERNEL);
> > -	if (!pd_data->domains)
> > -		return -ENOMEM;
> > -
> > -	clk[MT8173_CLK_MM] = devm_clk_get(&pdev->dev, "mm");
> > -	if (IS_ERR(clk[MT8173_CLK_MM]))
> > -		return PTR_ERR(clk[MT8173_CLK_MM]);
> > -
> > -	clk[MT8173_CLK_MFG] = devm_clk_get(&pdev->dev, "mfg");
> > -	if (IS_ERR(clk[MT8173_CLK_MFG]))
> > -		return PTR_ERR(clk[MT8173_CLK_MFG]);
> > -
> > -	clk[MT8173_CLK_VENC] = devm_clk_get(&pdev->dev, "venc");
> > -	if (IS_ERR(clk[MT8173_CLK_VENC]))
> > -		return PTR_ERR(clk[MT8173_CLK_VENC]);
> > -
> > -	clk[MT8173_CLK_VENC_LT] = devm_clk_get(&pdev->dev, "venc_lt");
> > -	if (IS_ERR(clk[MT8173_CLK_VENC_LT]))
> > -		return PTR_ERR(clk[MT8173_CLK_VENC_LT]);
> > +		return ERR_CAST(scp->base);
> >
> >  	scp->infracfg = syscon_regmap_lookup_by_phandle(pdev->dev.of_node,
> >  			"infracfg");
> >  	if (IS_ERR(scp->infracfg)) {
> >  		dev_err(&pdev->dev, "Cannot find infracfg controller: %ld\n",
> >  				PTR_ERR(scp->infracfg));
> > -		return PTR_ERR(scp->infracfg);
> > +		return ERR_CAST(scp->infracfg);
> >  	}
> >
> > -	for (i = 0; i < NUM_DOMAINS; i++) {
> > +	scp->domains = devm_kzalloc(&pdev->dev,
> > +				sizeof(*scp->domains) * num, GFP_KERNEL);
> > +	if (!scp->domains)
> > +		return ERR_PTR(-ENOMEM);
> > +
> > +	pd_data = &scp->pd_data;
> > +
> > +	pd_data->domains = devm_kzalloc(&pdev->dev,
> > +			sizeof(*pd_data->domains) * num, GFP_KERNEL);
> > +	if (!pd_data->domains)
> > +		return ERR_PTR(-ENOMEM);
> > +
> 
> While at it, please leave this block of code between
> devm_ioremap_resource(...) and syscon_regmap_lookup_by_phandle(...)

OK, I'll change it in next patch.


Best regards,

James

^ permalink raw reply

* [PATCH v13 4/4] arm: dts: mt2701: Use real clock for UARTs
From: Erin Lo @ 2016-09-26  9:26 UTC (permalink / raw)
  To: linux-arm-kernel
In-Reply-To: <1474881991-17429-1-git-send-email-erin.lo@mediatek.com>

We used to use a fixed rate clock for the UARTs. Now that we have clock
support we can associate the correct clocks to the UARTs and drop the
26MHz fixed rate UART clock.

Signed-off-by: Erin Lo <erin.lo@mediatek.com>
---
 arch/arm/boot/dts/mt2701.dtsi | 18 ++++++++----------
 1 file changed, 8 insertions(+), 10 deletions(-)

diff --git a/arch/arm/boot/dts/mt2701.dtsi b/arch/arm/boot/dts/mt2701.dtsi
index c9a8dbf..7eab6f4 100644
--- a/arch/arm/boot/dts/mt2701.dtsi
+++ b/arch/arm/boot/dts/mt2701.dtsi
@@ -73,12 +73,6 @@
 		#clock-cells = <0>;
 	};
 
-	uart_clk: dummy26m {
-		compatible = "fixed-clock";
-		clock-frequency = <26000000>;
-		#clock-cells = <0>;
-	};
-
 	clk26m: oscillator at 0 {
 		compatible = "fixed-clock";
 		#clock-cells = <0>;
@@ -186,7 +180,8 @@
 			     "mediatek,mt6577-uart";
 		reg = <0 0x11002000 0 0x400>;
 		interrupts = <GIC_SPI 51 IRQ_TYPE_LEVEL_LOW>;
-		clocks = <&uart_clk>;
+		clocks = <&pericfg CLK_PERI_UART0_SEL>, <&pericfg CLK_PERI_UART0>;
+		clock-names = "baud", "bus";
 		status = "disabled";
 	};
 
@@ -195,7 +190,8 @@
 			     "mediatek,mt6577-uart";
 		reg = <0 0x11003000 0 0x400>;
 		interrupts = <GIC_SPI 52 IRQ_TYPE_LEVEL_LOW>;
-		clocks = <&uart_clk>;
+		clocks = <&pericfg CLK_PERI_UART1_SEL>, <&pericfg CLK_PERI_UART1>;
+		clock-names = "baud", "bus";
 		status = "disabled";
 	};
 
@@ -204,7 +200,8 @@
 			     "mediatek,mt6577-uart";
 		reg = <0 0x11004000 0 0x400>;
 		interrupts = <GIC_SPI 53 IRQ_TYPE_LEVEL_LOW>;
-		clocks = <&uart_clk>;
+		clocks = <&pericfg CLK_PERI_UART2_SEL>, <&pericfg CLK_PERI_UART2>;
+		clock-names = "baud", "bus";
 		status = "disabled";
 	};
 
@@ -213,7 +210,8 @@
 			     "mediatek,mt6577-uart";
 		reg = <0 0x11005000 0 0x400>;
 		interrupts = <GIC_SPI 54 IRQ_TYPE_LEVEL_LOW>;
-		clocks = <&uart_clk>;
+		clocks = <&pericfg CLK_PERI_UART3_SEL>, <&pericfg CLK_PERI_UART3>;
+		clock-names = "baud", "bus";
 		status = "disabled";
 	};
 };
-- 
1.9.1

^ permalink raw reply related

* [PATCH v13 3/4] arm: dts: mt2701: Add clock controller device nodes
From: Erin Lo @ 2016-09-26  9:26 UTC (permalink / raw)
  To: linux-arm-kernel
In-Reply-To: <1474881991-17429-1-git-send-email-erin.lo@mediatek.com>

From: James Liao <jamesjj.liao@mediatek.com>

Add clock controller nodes for MT2701, include topckgen, infracfg,
pericfg, apmixedsys, mmsys, imgsys, vdecsys, hifsys, ethsys and
bdpsys. This patch also add two oscillators that provide clocks for
MT2701.

Signed-off-by: James Liao <jamesjj.liao@mediatek.com>
Signed-off-by: Erin Lo <erin.lo@mediatek.com>
---
 arch/arm/boot/dts/mt2701.dtsi | 42 ++++++++++++++++++++++++++++++++++++++++++
 1 file changed, 42 insertions(+)

diff --git a/arch/arm/boot/dts/mt2701.dtsi b/arch/arm/boot/dts/mt2701.dtsi
index 18596a2..c9a8dbf 100644
--- a/arch/arm/boot/dts/mt2701.dtsi
+++ b/arch/arm/boot/dts/mt2701.dtsi
@@ -12,8 +12,10 @@
  * GNU General Public License for more details.
  */
 
+#include <dt-bindings/clock/mt2701-clk.h>
 #include <dt-bindings/interrupt-controller/irq.h>
 #include <dt-bindings/interrupt-controller/arm-gic.h>
+#include <dt-bindings/reset/mt2701-resets.h>
 #include "skeleton64.dtsi"
 #include "mt2701-pinfunc.h"
 
@@ -77,6 +79,20 @@
 		#clock-cells = <0>;
 	};
 
+	clk26m: oscillator at 0 {
+		compatible = "fixed-clock";
+		#clock-cells = <0>;
+		clock-frequency = <26000000>;
+		clock-output-names = "clk26m";
+	};
+
+	rtc32k: oscillator at 1 {
+		compatible = "fixed-clock";
+		#clock-cells = <0>;
+		clock-frequency = <32000>;
+		clock-output-names = "rtc32k";
+	};
+
 	timer {
 		compatible = "arm,armv7-timer";
 		interrupt-parent = <&gic>;
@@ -104,6 +120,26 @@
 		reg = <0 0x10005000 0 0x1000>;
 	};
 
+	topckgen: syscon at 10000000 {
+		compatible = "mediatek,mt2701-topckgen", "syscon";
+		reg = <0 0x10000000 0 0x1000>;
+		#clock-cells = <1>;
+	};
+
+	infracfg: syscon at 10001000 {
+		compatible = "mediatek,mt2701-infracfg", "syscon";
+		reg = <0 0x10001000 0 0x1000>;
+		#clock-cells = <1>;
+		#reset-cells = <1>;
+	};
+
+	pericfg: syscon at 10003000 {
+		compatible = "mediatek,mt2701-pericfg", "syscon";
+		reg = <0 0x10003000 0 0x1000>;
+		#clock-cells = <1>;
+		#reset-cells = <1>;
+	};
+
 	watchdog: watchdog at 10007000 {
 		compatible = "mediatek,mt2701-wdt",
 			     "mediatek,mt6589-wdt";
@@ -128,6 +164,12 @@
 		reg = <0 0x10200100 0 0x1c>;
 	};
 
+	apmixedsys: syscon at 10209000 {
+		compatible = "mediatek,mt2701-apmixedsys", "syscon";
+		reg = <0 0x10209000 0 0x1000>;
+		#clock-cells = <1>;
+	};
+
 	gic: interrupt-controller at 10211000 {
 		compatible = "arm,cortex-a7-gic";
 		interrupt-controller;
-- 
1.9.1

^ permalink raw reply related

* [PATCH v13 2/4] reset: mediatek: Add MT2701 reset driver
From: Erin Lo @ 2016-09-26  9:26 UTC (permalink / raw)
  To: linux-arm-kernel
In-Reply-To: <1474881991-17429-1-git-send-email-erin.lo@mediatek.com>

From: Shunli Wang <shunli.wang@mediatek.com>

In infrasys and perifsys, there are many reset
control bits for kinds of modules. These bits are
used as actual reset controllers to be registered
into kernel's generic reset controller framework.

Signed-off-by: Shunli Wang <shunli.wang@mediatek.com>
Signed-off-by: James Liao <jamesjj.liao@mediatek.com>
Signed-off-by: Erin Lo <erin.lo@mediatek.com>
Tested-by: John Crispin <blogic@openwrt.org>
Acked-by: Philipp Zabel <p.zabel@pengutronix.de>
---
 drivers/clk/mediatek/clk-mt2701-hif.c |  6 +++++-
 drivers/clk/mediatek/clk-mt2701.c     | 12 ++++++++++--
 2 files changed, 15 insertions(+), 3 deletions(-)

diff --git a/drivers/clk/mediatek/clk-mt2701-hif.c b/drivers/clk/mediatek/clk-mt2701-hif.c
index e2b0039..4c0688f 100644
--- a/drivers/clk/mediatek/clk-mt2701-hif.c
+++ b/drivers/clk/mediatek/clk-mt2701-hif.c
@@ -53,8 +53,12 @@ static int mtk_hifsys_init(struct platform_device *pdev)
 						clk_data);
 
 	r = of_clk_add_provider(node, of_clk_src_onecell_get, clk_data);
+	if (r)
+		return r;
 
-	return r;
+	mtk_register_reset_controller(node, 1, 0x34);
+
+	return 0;
 }
 
 static const struct of_device_id of_match_clk_mt2701_hif[] = {
diff --git a/drivers/clk/mediatek/clk-mt2701.c b/drivers/clk/mediatek/clk-mt2701.c
index c225256..b2148b6 100644
--- a/drivers/clk/mediatek/clk-mt2701.c
+++ b/drivers/clk/mediatek/clk-mt2701.c
@@ -802,8 +802,12 @@ static int mtk_infrasys_init(struct platform_device *pdev)
 						infra_clk_data);
 
 	r = of_clk_add_provider(node, of_clk_src_onecell_get, infra_clk_data);
+	if (r)
+		return r;
 
-	return r;
+	mtk_register_reset_controller(node, 2, 0x30);
+
+	return 0;
 }
 
 static const struct mtk_gate_regs peri0_cg_regs = {
@@ -920,8 +924,12 @@ static int mtk_pericfg_init(struct platform_device *pdev)
 			&lock, clk_data);
 
 	r = of_clk_add_provider(node, of_clk_src_onecell_get, clk_data);
+	if (r)
+		return r;
 
-	return r;
+	mtk_register_reset_controller(node, 2, 0x0);
+
+	return 0;
 }
 
 #define MT8590_PLL_FMAX		(2000 * MHZ)
-- 
1.9.1

^ permalink raw reply related

* [PATCH v13 1/4] clk: mediatek: Add MT2701 clock support
From: Erin Lo @ 2016-09-26  9:26 UTC (permalink / raw)
  To: linux-arm-kernel
In-Reply-To: <1474881991-17429-1-git-send-email-erin.lo@mediatek.com>

From: Shunli Wang <shunli.wang@mediatek.com>

Add MT2701 clock support, include topckgen, apmixedsys,
infracfg, pericfg and subsystem clocks.

Signed-off-by: Shunli Wang <shunli.wang@mediatek.com>
Signed-off-by: James Liao <jamesjj.liao@mediatek.com>
Signed-off-by: Erin Lo <erin.lo@mediatek.com>
Tested-by: John Crispin <blogic@openwrt.org>
---
 drivers/clk/mediatek/Kconfig           |   43 ++
 drivers/clk/mediatek/Makefile          |    7 +
 drivers/clk/mediatek/clk-gate.c        |   52 ++
 drivers/clk/mediatek/clk-gate.h        |    2 +
 drivers/clk/mediatek/clk-mt2701-bdp.c  |  148 +++++
 drivers/clk/mediatek/clk-mt2701-eth.c  |   90 +++
 drivers/clk/mediatek/clk-mt2701-hif.c  |   87 +++
 drivers/clk/mediatek/clk-mt2701-img.c  |   90 +++
 drivers/clk/mediatek/clk-mt2701-mm.c   |  133 ++++
 drivers/clk/mediatek/clk-mt2701-vdec.c |  101 +++
 drivers/clk/mediatek/clk-mt2701.c      | 1046 ++++++++++++++++++++++++++++++++
 drivers/clk/mediatek/clk-mtk.c         |   40 ++
 drivers/clk/mediatek/clk-mtk.h         |   41 +-
 drivers/clk/mediatek/clk-pll.c         |    1 +
 14 files changed, 1876 insertions(+), 5 deletions(-)
 create mode 100644 drivers/clk/mediatek/clk-mt2701-bdp.c
 create mode 100644 drivers/clk/mediatek/clk-mt2701-eth.c
 create mode 100644 drivers/clk/mediatek/clk-mt2701-hif.c
 create mode 100644 drivers/clk/mediatek/clk-mt2701-img.c
 create mode 100644 drivers/clk/mediatek/clk-mt2701-mm.c
 create mode 100644 drivers/clk/mediatek/clk-mt2701-vdec.c
 create mode 100644 drivers/clk/mediatek/clk-mt2701.c

diff --git a/drivers/clk/mediatek/Kconfig b/drivers/clk/mediatek/Kconfig
index 380c372..7202db5 100644
--- a/drivers/clk/mediatek/Kconfig
+++ b/drivers/clk/mediatek/Kconfig
@@ -6,6 +6,49 @@ config COMMON_CLK_MEDIATEK
 	---help---
 	  Mediatek SoCs' clock support.
 
+config COMMON_CLK_MT2701
+	bool "Clock driver for Mediatek MT2701"
+	select COMMON_CLK_MEDIATEK
+	default ARCH_MEDIATEK
+	---help---
+	  This driver supports Mediatek MT2701 basic clocks.
+
+config COMMON_CLK_MT2701_MMSYS
+	bool "Clock driver for Mediatek MT2701 mmsys"
+	select COMMON_CLK_MT2701
+	---help---
+	  This driver supports Mediatek MT2701 mmsys clocks.
+
+config COMMON_CLK_MT2701_IMGSYS
+	bool "Clock driver for Mediatek MT2701 imgsys"
+	select COMMON_CLK_MT2701
+	---help---
+	  This driver supports Mediatek MT2701 imgsys clocks.
+
+config COMMON_CLK_MT2701_VDECSYS
+	bool "Clock driver for Mediatek MT2701 vdecsys"
+	select COMMON_CLK_MT2701
+	---help---
+	  This driver supports Mediatek MT2701 vdecsys clocks.
+
+config COMMON_CLK_MT2701_HIFSYS
+	bool "Clock driver for Mediatek MT2701 hifsys"
+	select COMMON_CLK_MT2701
+	---help---
+	  This driver supports Mediatek MT2701 hifsys clocks.
+
+config COMMON_CLK_MT2701_ETHSYS
+	bool "Clock driver for Mediatek MT2701 ethsys"
+	select COMMON_CLK_MT2701
+	---help---
+	  This driver supports Mediatek MT2701 ethsys clocks.
+
+config COMMON_CLK_MT2701_BDPSYS
+	bool "Clock driver for Mediatek MT2701 bdpsys"
+	select COMMON_CLK_MT2701
+	---help---
+	  This driver supports Mediatek MT2701 bdpsys clocks.
+
 config COMMON_CLK_MT8135
 	bool "Clock driver for Mediatek MT8135"
 	select COMMON_CLK_MEDIATEK
diff --git a/drivers/clk/mediatek/Makefile b/drivers/clk/mediatek/Makefile
index 32e7222..19ae7ef 100644
--- a/drivers/clk/mediatek/Makefile
+++ b/drivers/clk/mediatek/Makefile
@@ -1,4 +1,11 @@
 obj-$(CONFIG_COMMON_CLK_MEDIATEK) += clk-mtk.o clk-pll.o clk-gate.o clk-apmixed.o
 obj-$(CONFIG_RESET_CONTROLLER) += reset.o
+obj-$(CONFIG_COMMON_CLK_MT2701) += clk-mt2701.o
+obj-$(CONFIG_COMMON_CLK_MT2701_BDPSYS) += clk-mt2701-bdp.o
+obj-$(CONFIG_COMMON_CLK_MT2701_ETHSYS) += clk-mt2701-eth.o
+obj-$(CONFIG_COMMON_CLK_MT2701_HIFSYS) += clk-mt2701-hif.o
+obj-$(CONFIG_COMMON_CLK_MT2701_IMGSYS) += clk-mt2701-img.o
+obj-$(CONFIG_COMMON_CLK_MT2701_MMSYS) += clk-mt2701-mm.o
+obj-$(CONFIG_COMMON_CLK_MT2701_VDECSYS) += clk-mt2701-vdec.o
 obj-$(CONFIG_COMMON_CLK_MT8135) += clk-mt8135.o
 obj-$(CONFIG_COMMON_CLK_MT8173) += clk-mt8173.o
diff --git a/drivers/clk/mediatek/clk-gate.c b/drivers/clk/mediatek/clk-gate.c
index d8787bf..934bf0e 100644
--- a/drivers/clk/mediatek/clk-gate.c
+++ b/drivers/clk/mediatek/clk-gate.c
@@ -61,6 +61,22 @@ static void mtk_cg_clr_bit(struct clk_hw *hw)
 	regmap_write(cg->regmap, cg->clr_ofs, BIT(cg->bit));
 }
 
+static void mtk_cg_set_bit_no_setclr(struct clk_hw *hw)
+{
+	struct mtk_clk_gate *cg = to_mtk_clk_gate(hw);
+	u32 cgbit = BIT(cg->bit);
+
+	regmap_update_bits(cg->regmap, cg->sta_ofs, cgbit, cgbit);
+}
+
+static void mtk_cg_clr_bit_no_setclr(struct clk_hw *hw)
+{
+	struct mtk_clk_gate *cg = to_mtk_clk_gate(hw);
+	u32 cgbit = BIT(cg->bit);
+
+	regmap_update_bits(cg->regmap, cg->sta_ofs, cgbit, 0);
+}
+
 static int mtk_cg_enable(struct clk_hw *hw)
 {
 	mtk_cg_clr_bit(hw);
@@ -85,6 +101,30 @@ static void mtk_cg_disable_inv(struct clk_hw *hw)
 	mtk_cg_clr_bit(hw);
 }
 
+static int mtk_cg_enable_no_setclr(struct clk_hw *hw)
+{
+	mtk_cg_clr_bit_no_setclr(hw);
+
+	return 0;
+}
+
+static void mtk_cg_disable_no_setclr(struct clk_hw *hw)
+{
+	mtk_cg_set_bit_no_setclr(hw);
+}
+
+static int mtk_cg_enable_inv_no_setclr(struct clk_hw *hw)
+{
+	mtk_cg_set_bit_no_setclr(hw);
+
+	return 0;
+}
+
+static void mtk_cg_disable_inv_no_setclr(struct clk_hw *hw)
+{
+	mtk_cg_clr_bit_no_setclr(hw);
+}
+
 const struct clk_ops mtk_clk_gate_ops_setclr = {
 	.is_enabled	= mtk_cg_bit_is_cleared,
 	.enable		= mtk_cg_enable,
@@ -97,6 +137,18 @@ const struct clk_ops mtk_clk_gate_ops_setclr_inv = {
 	.disable	= mtk_cg_disable_inv,
 };
 
+const struct clk_ops mtk_clk_gate_ops_no_setclr = {
+	.is_enabled	= mtk_cg_bit_is_cleared,
+	.enable		= mtk_cg_enable_no_setclr,
+	.disable	= mtk_cg_disable_no_setclr,
+};
+
+const struct clk_ops mtk_clk_gate_ops_no_setclr_inv = {
+	.is_enabled	= mtk_cg_bit_is_set,
+	.enable		= mtk_cg_enable_inv_no_setclr,
+	.disable	= mtk_cg_disable_inv_no_setclr,
+};
+
 struct clk *mtk_clk_register_gate(
 		const char *name,
 		const char *parent_name,
diff --git a/drivers/clk/mediatek/clk-gate.h b/drivers/clk/mediatek/clk-gate.h
index b182160..72ef89b 100644
--- a/drivers/clk/mediatek/clk-gate.h
+++ b/drivers/clk/mediatek/clk-gate.h
@@ -36,6 +36,8 @@ static inline struct mtk_clk_gate *to_mtk_clk_gate(struct clk_hw *hw)
 
 extern const struct clk_ops mtk_clk_gate_ops_setclr;
 extern const struct clk_ops mtk_clk_gate_ops_setclr_inv;
+extern const struct clk_ops mtk_clk_gate_ops_no_setclr;
+extern const struct clk_ops mtk_clk_gate_ops_no_setclr_inv;
 
 struct clk *mtk_clk_register_gate(
 		const char *name,
diff --git a/drivers/clk/mediatek/clk-mt2701-bdp.c b/drivers/clk/mediatek/clk-mt2701-bdp.c
new file mode 100644
index 0000000..dbf6ab2
--- /dev/null
+++ b/drivers/clk/mediatek/clk-mt2701-bdp.c
@@ -0,0 +1,148 @@
+/*
+ * Copyright (c) 2014 MediaTek Inc.
+ * Author: Shunli Wang <shunli.wang@mediatek.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ */
+
+#include <linux/clk-provider.h>
+#include <linux/platform_device.h>
+
+#include "clk-mtk.h"
+#include "clk-gate.h"
+
+#include <dt-bindings/clock/mt2701-clk.h>
+
+static const struct mtk_gate_regs bdp0_cg_regs = {
+	.set_ofs = 0x0104,
+	.clr_ofs = 0x0108,
+	.sta_ofs = 0x0100,
+};
+
+static const struct mtk_gate_regs bdp1_cg_regs = {
+	.set_ofs = 0x0114,
+	.clr_ofs = 0x0118,
+	.sta_ofs = 0x0110,
+};
+
+#define GATE_BDP0(_id, _name, _parent, _shift) {	\
+		.id = _id,				\
+		.name = _name,				\
+		.parent_name = _parent,			\
+		.regs = &bdp0_cg_regs,			\
+		.shift = _shift,			\
+		.ops = &mtk_clk_gate_ops_setclr_inv,	\
+	}
+
+#define GATE_BDP1(_id, _name, _parent, _shift) {	\
+		.id = _id,				\
+		.name = _name,				\
+		.parent_name = _parent,			\
+		.regs = &bdp1_cg_regs,			\
+		.shift = _shift,			\
+		.ops = &mtk_clk_gate_ops_setclr_inv,	\
+	}
+
+static const struct mtk_gate bdp_clks[] = {
+	GATE_BDP0(CLK_BDP_BRG_BA, "brg_baclk", "mm_sel", 0),
+	GATE_BDP0(CLK_BDP_BRG_DRAM, "brg_dram", "mm_sel", 1),
+	GATE_BDP0(CLK_BDP_LARB_DRAM, "larb_dram", "mm_sel", 2),
+	GATE_BDP0(CLK_BDP_WR_VDI_PXL, "wr_vdi_pxl", "hdmi_0_deep340m", 3),
+	GATE_BDP0(CLK_BDP_WR_VDI_DRAM, "wr_vdi_dram", "mm_sel", 4),
+	GATE_BDP0(CLK_BDP_WR_B, "wr_bclk", "mm_sel", 5),
+	GATE_BDP0(CLK_BDP_DGI_IN, "dgi_in", "dpi1_sel", 6),
+	GATE_BDP0(CLK_BDP_DGI_OUT, "dgi_out", "dpi1_sel", 7),
+	GATE_BDP0(CLK_BDP_FMT_MAST_27, "fmt_mast_27", "dpi1_sel", 8),
+	GATE_BDP0(CLK_BDP_FMT_B, "fmt_bclk", "mm_sel", 9),
+	GATE_BDP0(CLK_BDP_OSD_B, "osd_bclk", "mm_sel", 10),
+	GATE_BDP0(CLK_BDP_OSD_DRAM, "osd_dram", "mm_sel", 11),
+	GATE_BDP0(CLK_BDP_OSD_AGENT, "osd_agent", "osd_sel", 12),
+	GATE_BDP0(CLK_BDP_OSD_PXL, "osd_pxl", "dpi1_sel", 13),
+	GATE_BDP0(CLK_BDP_RLE_B, "rle_bclk", "mm_sel", 14),
+	GATE_BDP0(CLK_BDP_RLE_AGENT, "rle_agent", "mm_sel", 15),
+	GATE_BDP0(CLK_BDP_RLE_DRAM, "rle_dram", "mm_sel", 16),
+	GATE_BDP0(CLK_BDP_F27M, "f27m", "di_sel", 17),
+	GATE_BDP0(CLK_BDP_F27M_VDOUT, "f27m_vdout", "di_sel", 18),
+	GATE_BDP0(CLK_BDP_F27_74_74, "f27_74_74", "di_sel", 19),
+	GATE_BDP0(CLK_BDP_F2FS, "f2fs", "di_sel", 20),
+	GATE_BDP0(CLK_BDP_F2FS74_148, "f2fs74_148", "di_sel", 21),
+	GATE_BDP0(CLK_BDP_FB, "fbclk", "mm_sel", 22),
+	GATE_BDP0(CLK_BDP_VDO_DRAM, "vdo_dram", "mm_sel", 23),
+	GATE_BDP0(CLK_BDP_VDO_2FS, "vdo_2fs", "di_sel", 24),
+	GATE_BDP0(CLK_BDP_VDO_B, "vdo_bclk", "mm_sel", 25),
+	GATE_BDP0(CLK_BDP_WR_DI_PXL, "wr_di_pxl", "di_sel", 26),
+	GATE_BDP0(CLK_BDP_WR_DI_DRAM, "wr_di_dram", "mm_sel", 27),
+	GATE_BDP0(CLK_BDP_WR_DI_B, "wr_di_bclk", "mm_sel", 28),
+	GATE_BDP0(CLK_BDP_NR_PXL, "nr_pxl", "nr_sel", 29),
+	GATE_BDP0(CLK_BDP_NR_DRAM, "nr_dram", "mm_sel", 30),
+	GATE_BDP0(CLK_BDP_NR_B, "nr_bclk", "mm_sel", 31),
+	GATE_BDP1(CLK_BDP_RX_F, "rx_fclk", "hadds2_fbclk", 0),
+	GATE_BDP1(CLK_BDP_RX_X, "rx_xclk", "clk26m", 1),
+	GATE_BDP1(CLK_BDP_RXPDT, "rxpdtclk", "hdmi_0_pix340m", 2),
+	GATE_BDP1(CLK_BDP_RX_CSCL_N, "rx_cscl_n", "clk26m", 3),
+	GATE_BDP1(CLK_BDP_RX_CSCL, "rx_cscl", "clk26m", 4),
+	GATE_BDP1(CLK_BDP_RX_DDCSCL_N, "rx_ddcscl_n", "hdmi_scl_rx", 5),
+	GATE_BDP1(CLK_BDP_RX_DDCSCL, "rx_ddcscl", "hdmi_scl_rx", 6),
+	GATE_BDP1(CLK_BDP_RX_VCO, "rx_vcoclk", "hadds2pll_294m", 7),
+	GATE_BDP1(CLK_BDP_RX_DP, "rx_dpclk", "hdmi_0_pll340m", 8),
+	GATE_BDP1(CLK_BDP_RX_P, "rx_pclk", "hdmi_0_pll340m", 9),
+	GATE_BDP1(CLK_BDP_RX_M, "rx_mclk", "hadds2pll_294m", 10),
+	GATE_BDP1(CLK_BDP_RX_PLL, "rx_pllclk", "hdmi_0_pix340m", 11),
+	GATE_BDP1(CLK_BDP_BRG_RT_B, "brg_rt_bclk", "mm_sel", 12),
+	GATE_BDP1(CLK_BDP_BRG_RT_DRAM, "brg_rt_dram", "mm_sel", 13),
+	GATE_BDP1(CLK_BDP_LARBRT_DRAM, "larbrt_dram", "mm_sel", 14),
+	GATE_BDP1(CLK_BDP_TMDS_SYN, "tmds_syn", "hdmi_0_pll340m", 15),
+	GATE_BDP1(CLK_BDP_HDMI_MON, "hdmi_mon", "hdmi_0_pll340m", 16),
+};
+
+static int mtk_bdpsys_init(struct platform_device *pdev)
+{
+	struct clk_onecell_data *clk_data;
+	int r;
+	struct device_node *node = pdev->dev.of_node;
+
+	clk_data = mtk_alloc_clk_data(CLK_BDP_NR);
+
+	mtk_clk_register_gates(node, bdp_clks, ARRAY_SIZE(bdp_clks),
+						clk_data);
+
+	r = of_clk_add_provider(node, of_clk_src_onecell_get, clk_data);
+
+	return r;
+}
+
+static const struct of_device_id of_match_clk_mt2701_bdp[] = {
+	{ .compatible = "mediatek,mt2701-bdpsys", },
+	{}
+};
+
+static int clk_mt2701_bdp_probe(struct platform_device *pdev)
+{
+	int r;
+
+	r = mtk_bdpsys_init(pdev);
+	if (r) {
+		dev_err(&pdev->dev,
+			"could not register clock provider: %s: %d\n",
+			pdev->name, r);
+	}
+
+	return r;
+}
+
+static struct platform_driver clk_mt2701_bdp_drv = {
+	.probe = clk_mt2701_bdp_probe,
+	.driver = {
+		.name = "clk-mt2701-bdp",
+		.of_match_table = of_match_clk_mt2701_bdp,
+	},
+};
+
+builtin_platform_driver(clk_mt2701_bdp_drv);
diff --git a/drivers/clk/mediatek/clk-mt2701-eth.c b/drivers/clk/mediatek/clk-mt2701-eth.c
new file mode 100644
index 0000000..b2a71a4
--- /dev/null
+++ b/drivers/clk/mediatek/clk-mt2701-eth.c
@@ -0,0 +1,90 @@
+/*
+ * Copyright (c) 2014 MediaTek Inc.
+ * Author: Shunli Wang <shunli.wang@mediatek.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ */
+
+#include <linux/clk-provider.h>
+#include <linux/platform_device.h>
+
+#include "clk-mtk.h"
+#include "clk-gate.h"
+
+#include <dt-bindings/clock/mt2701-clk.h>
+
+static const struct mtk_gate_regs eth_cg_regs = {
+	.sta_ofs = 0x0030,
+};
+
+#define GATE_ETH(_id, _name, _parent, _shift) {		\
+		.id = _id,				\
+		.name = _name,				\
+		.parent_name = _parent,			\
+		.regs = &eth_cg_regs,			\
+		.shift = _shift,			\
+		.ops = &mtk_clk_gate_ops_no_setclr_inv,	\
+	}
+
+static const struct mtk_gate eth_clks[] = {
+	GATE_ETH(CLK_ETHSYS_HSDMA, "hsdma_clk", "ethif_sel", 5),
+	GATE_ETH(CLK_ETHSYS_ESW, "esw_clk", "ethpll_500m_ck", 6),
+	GATE_ETH(CLK_ETHSYS_GP2, "gp2_clk", "trgpll", 7),
+	GATE_ETH(CLK_ETHSYS_GP1, "gp1_clk", "ethpll_500m_ck", 8),
+	GATE_ETH(CLK_ETHSYS_PCM, "pcm_clk", "ethif_sel", 11),
+	GATE_ETH(CLK_ETHSYS_GDMA, "gdma_clk", "ethif_sel", 14),
+	GATE_ETH(CLK_ETHSYS_I2S, "i2s_clk", "ethif_sel", 17),
+	GATE_ETH(CLK_ETHSYS_CRYPTO, "crypto_clk", "ethif_sel", 29),
+};
+
+static int mtk_ethsys_init(struct platform_device *pdev)
+{
+	struct clk_onecell_data *clk_data;
+	int r;
+	struct device_node *node = pdev->dev.of_node;
+
+	clk_data = mtk_alloc_clk_data(CLK_ETHSYS_NR);
+
+	mtk_clk_register_gates(node, eth_clks, ARRAY_SIZE(eth_clks),
+						clk_data);
+
+	r = of_clk_add_provider(node, of_clk_src_onecell_get, clk_data);
+
+	return r;
+}
+
+static const struct of_device_id of_match_clk_mt2701_eth[] = {
+	{ .compatible = "mediatek,mt2701-ethsys", },
+	{}
+};
+
+static int clk_mt2701_eth_probe(struct platform_device *pdev)
+{
+	int r;
+
+	r = mtk_ethsys_init(pdev);
+	if (r) {
+		dev_err(&pdev->dev,
+			"could not register clock provider: %s: %d\n",
+			pdev->name, r);
+	}
+
+	return r;
+}
+
+static struct platform_driver clk_mt2701_eth_drv = {
+	.probe = clk_mt2701_eth_probe,
+	.driver = {
+		.name = "clk-mt2701-eth",
+		.of_match_table = of_match_clk_mt2701_eth,
+	},
+};
+
+builtin_platform_driver(clk_mt2701_eth_drv);
diff --git a/drivers/clk/mediatek/clk-mt2701-hif.c b/drivers/clk/mediatek/clk-mt2701-hif.c
new file mode 100644
index 0000000..e2b0039
--- /dev/null
+++ b/drivers/clk/mediatek/clk-mt2701-hif.c
@@ -0,0 +1,87 @@
+/*
+ * Copyright (c) 2014 MediaTek Inc.
+ * Author: Shunli Wang <shunli.wang@mediatek.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ */
+
+#include <linux/clk-provider.h>
+#include <linux/platform_device.h>
+
+#include "clk-mtk.h"
+#include "clk-gate.h"
+
+#include <dt-bindings/clock/mt2701-clk.h>
+
+static const struct mtk_gate_regs hif_cg_regs = {
+	.sta_ofs = 0x0030,
+};
+
+#define GATE_HIF(_id, _name, _parent, _shift) {		\
+		.id = _id,				\
+		.name = _name,				\
+		.parent_name = _parent,			\
+		.regs = &hif_cg_regs,			\
+		.shift = _shift,			\
+		.ops = &mtk_clk_gate_ops_no_setclr_inv,	\
+	}
+
+static const struct mtk_gate hif_clks[] = {
+	GATE_HIF(CLK_HIFSYS_USB0PHY, "usb0_phy_clk", "ethpll_500m_ck", 21),
+	GATE_HIF(CLK_HIFSYS_USB1PHY, "usb1_phy_clk", "ethpll_500m_ck", 22),
+	GATE_HIF(CLK_HIFSYS_PCIE0, "pcie0_clk", "ethpll_500m_ck", 24),
+	GATE_HIF(CLK_HIFSYS_PCIE1, "pcie1_clk", "ethpll_500m_ck", 25),
+	GATE_HIF(CLK_HIFSYS_PCIE2, "pcie2_clk", "ethpll_500m_ck", 26),
+};
+
+static int mtk_hifsys_init(struct platform_device *pdev)
+{
+	struct clk_onecell_data *clk_data;
+	int r;
+	struct device_node *node = pdev->dev.of_node;
+
+	clk_data = mtk_alloc_clk_data(CLK_HIFSYS_NR);
+
+	mtk_clk_register_gates(node, hif_clks, ARRAY_SIZE(hif_clks),
+						clk_data);
+
+	r = of_clk_add_provider(node, of_clk_src_onecell_get, clk_data);
+
+	return r;
+}
+
+static const struct of_device_id of_match_clk_mt2701_hif[] = {
+	{ .compatible = "mediatek,mt2701-hifsys", },
+	{}
+};
+
+static int clk_mt2701_hif_probe(struct platform_device *pdev)
+{
+	int r;
+
+	r = mtk_hifsys_init(pdev);
+	if (r) {
+		dev_err(&pdev->dev,
+			"could not register clock provider: %s: %d\n",
+			pdev->name, r);
+	}
+
+	return r;
+}
+
+static struct platform_driver clk_mt2701_hif_drv = {
+	.probe = clk_mt2701_hif_probe,
+	.driver = {
+		.name = "clk-mt2701-hif",
+		.of_match_table = of_match_clk_mt2701_hif,
+	},
+};
+
+builtin_platform_driver(clk_mt2701_hif_drv);
diff --git a/drivers/clk/mediatek/clk-mt2701-img.c b/drivers/clk/mediatek/clk-mt2701-img.c
new file mode 100644
index 0000000..c8a7a73
--- /dev/null
+++ b/drivers/clk/mediatek/clk-mt2701-img.c
@@ -0,0 +1,90 @@
+/*
+ * Copyright (c) 2014 MediaTek Inc.
+ * Author: Shunli Wang <shunli.wang@mediatek.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ */
+
+#include <linux/clk-provider.h>
+#include <linux/platform_device.h>
+
+#include "clk-mtk.h"
+#include "clk-gate.h"
+
+#include <dt-bindings/clock/mt2701-clk.h>
+
+static const struct mtk_gate_regs img_cg_regs = {
+	.set_ofs = 0x0004,
+	.clr_ofs = 0x0008,
+	.sta_ofs = 0x0000,
+};
+
+#define GATE_IMG(_id, _name, _parent, _shift) {		\
+		.id = _id,				\
+		.name = _name,				\
+		.parent_name = _parent,			\
+		.regs = &img_cg_regs,			\
+		.shift = _shift,			\
+		.ops = &mtk_clk_gate_ops_setclr,	\
+	}
+
+static const struct mtk_gate img_clks[] = {
+	GATE_IMG(CLK_IMG_SMI_COMM, "img_smi_comm", "mm_sel", 0),
+	GATE_IMG(CLK_IMG_RESZ, "img_resz", "mm_sel", 1),
+	GATE_IMG(CLK_IMG_JPGDEC_SMI, "img_jpgdec_smi", "mm_sel", 5),
+	GATE_IMG(CLK_IMG_JPGDEC, "img_jpgdec", "mm_sel", 6),
+	GATE_IMG(CLK_IMG_VENC_LT, "img_venc_lt", "mm_sel", 8),
+	GATE_IMG(CLK_IMG_VENC, "img_venc", "mm_sel", 9),
+};
+
+static int mtk_imgsys_init(struct platform_device *pdev)
+{
+	struct clk_onecell_data *clk_data;
+	int r;
+	struct device_node *node = pdev->dev.of_node;
+
+	clk_data = mtk_alloc_clk_data(CLK_IMG_NR);
+
+	mtk_clk_register_gates(node, img_clks, ARRAY_SIZE(img_clks),
+						clk_data);
+
+	r = of_clk_add_provider(node, of_clk_src_onecell_get, clk_data);
+
+	return r;
+}
+
+static const struct of_device_id of_match_clk_mt2701_img[] = {
+	{ .compatible = "mediatek,mt2701-imgsys", },
+	{}
+};
+
+static int clk_mt2701_img_probe(struct platform_device *pdev)
+{
+	int r;
+
+	r = mtk_imgsys_init(pdev);
+	if (r) {
+		dev_err(&pdev->dev,
+			"could not register clock provider: %s: %d\n",
+			pdev->name, r);
+	}
+
+	return r;
+}
+
+static struct platform_driver clk_mt2701_img_drv = {
+	.probe = clk_mt2701_img_probe,
+	.driver = {
+		.name = "clk-mt2701-img",
+		.of_match_table = of_match_clk_mt2701_img,
+	},
+};
+
+builtin_platform_driver(clk_mt2701_img_drv);
diff --git a/drivers/clk/mediatek/clk-mt2701-mm.c b/drivers/clk/mediatek/clk-mt2701-mm.c
new file mode 100644
index 0000000..a63ea2f
--- /dev/null
+++ b/drivers/clk/mediatek/clk-mt2701-mm.c
@@ -0,0 +1,133 @@
+/*
+ * Copyright (c) 2014 MediaTek Inc.
+ * Author: Shunli Wang <shunli.wang@mediatek.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ */
+
+#include <linux/clk-provider.h>
+#include <linux/platform_device.h>
+
+#include "clk-mtk.h"
+#include "clk-gate.h"
+
+#include <dt-bindings/clock/mt2701-clk.h>
+
+static const struct mtk_gate_regs disp0_cg_regs = {
+	.set_ofs = 0x0104,
+	.clr_ofs = 0x0108,
+	.sta_ofs = 0x0100,
+};
+
+static const struct mtk_gate_regs disp1_cg_regs = {
+	.set_ofs = 0x0114,
+	.clr_ofs = 0x0118,
+	.sta_ofs = 0x0110,
+};
+
+#define GATE_DISP0(_id, _name, _parent, _shift) {	\
+		.id = _id,				\
+		.name = _name,				\
+		.parent_name = _parent,			\
+		.regs = &disp0_cg_regs,			\
+		.shift = _shift,			\
+		.ops = &mtk_clk_gate_ops_setclr,	\
+	}
+
+#define GATE_DISP1(_id, _name, _parent, _shift) {	\
+		.id = _id,				\
+		.name = _name,				\
+		.parent_name = _parent,			\
+		.regs = &disp1_cg_regs,			\
+		.shift = _shift,			\
+		.ops = &mtk_clk_gate_ops_setclr,	\
+	}
+
+static const struct mtk_gate mm_clks[] = {
+	GATE_DISP0(CLK_MM_SMI_COMMON, "mm_smi_comm", "mm_sel", 0),
+	GATE_DISP0(CLK_MM_SMI_LARB0, "mm_smi_larb0", "mm_sel", 1),
+	GATE_DISP0(CLK_MM_CMDQ, "mm_cmdq", "mm_sel", 2),
+	GATE_DISP0(CLK_MM_MUTEX, "mm_mutex", "mm_sel", 3),
+	GATE_DISP0(CLK_MM_DISP_COLOR, "mm_disp_color", "mm_sel", 4),
+	GATE_DISP0(CLK_MM_DISP_BLS, "mm_disp_bls", "mm_sel", 5),
+	GATE_DISP0(CLK_MM_DISP_WDMA, "mm_disp_wdma", "mm_sel", 6),
+	GATE_DISP0(CLK_MM_DISP_RDMA, "mm_disp_rdma", "mm_sel", 7),
+	GATE_DISP0(CLK_MM_DISP_OVL, "mm_disp_ovl", "mm_sel", 8),
+	GATE_DISP0(CLK_MM_MDP_TDSHP, "mm_mdp_tdshp", "mm_sel", 9),
+	GATE_DISP0(CLK_MM_MDP_WROT, "mm_mdp_wrot", "mm_sel", 10),
+	GATE_DISP0(CLK_MM_MDP_WDMA, "mm_mdp_wdma", "mm_sel", 11),
+	GATE_DISP0(CLK_MM_MDP_RSZ1, "mm_mdp_rsz1", "mm_sel", 12),
+	GATE_DISP0(CLK_MM_MDP_RSZ0, "mm_mdp_rsz0", "mm_sel", 13),
+	GATE_DISP0(CLK_MM_MDP_RDMA, "mm_mdp_rdma", "mm_sel", 14),
+	GATE_DISP0(CLK_MM_MDP_BLS_26M, "mm_mdp_bls_26m", "pwm_sel", 15),
+	GATE_DISP0(CLK_MM_CAM_MDP, "mm_cam_mdp", "mm_sel", 16),
+	GATE_DISP0(CLK_MM_FAKE_ENG, "mm_fake_eng", "mm_sel", 17),
+	GATE_DISP0(CLK_MM_MUTEX_32K, "mm_mutex_32k", "rtc_sel", 18),
+	GATE_DISP0(CLK_MM_DISP_RDMA1, "mm_disp_rdma1", "mm_sel", 19),
+	GATE_DISP0(CLK_MM_DISP_UFOE, "mm_disp_ufoe", "mm_sel", 20),
+	GATE_DISP1(CLK_MM_DSI_ENGINE, "mm_dsi_eng", "mm_sel", 0),
+	GATE_DISP1(CLK_MM_DSI_DIG, "mm_dsi_dig", "dsi0_lntc_dsi", 1),
+	GATE_DISP1(CLK_MM_DPI_DIGL, "mm_dpi_digl", "dpi0_sel", 2),
+	GATE_DISP1(CLK_MM_DPI_ENGINE, "mm_dpi_eng", "mm_sel", 3),
+	GATE_DISP1(CLK_MM_DPI1_DIGL, "mm_dpi1_digl", "dpi1_sel", 4),
+	GATE_DISP1(CLK_MM_DPI1_ENGINE, "mm_dpi1_eng", "mm_sel", 5),
+	GATE_DISP1(CLK_MM_TVE_OUTPUT, "mm_tve_output", "tve_sel", 6),
+	GATE_DISP1(CLK_MM_TVE_INPUT, "mm_tve_input", "dpi0_sel", 7),
+	GATE_DISP1(CLK_MM_HDMI_PIXEL, "mm_hdmi_pixel", "dpi1_sel", 8),
+	GATE_DISP1(CLK_MM_HDMI_PLL, "mm_hdmi_pll", "hdmi_sel", 9),
+	GATE_DISP1(CLK_MM_HDMI_AUDIO, "mm_hdmi_audio", "apll_sel", 10),
+	GATE_DISP1(CLK_MM_HDMI_SPDIF, "mm_hdmi_spdif", "apll_sel", 11),
+	GATE_DISP1(CLK_MM_TVE_FMM, "mm_tve_fmm", "mm_sel", 14),
+};
+
+static int mtk_mmsys_init(struct platform_device *pdev)
+{
+	struct clk_onecell_data *clk_data;
+	int r;
+	struct device_node *node = pdev->dev.of_node;
+
+	clk_data = mtk_alloc_clk_data(CLK_MM_NR);
+
+	mtk_clk_register_gates(node, mm_clks, ARRAY_SIZE(mm_clks),
+						clk_data);
+
+	r = of_clk_add_provider(node, of_clk_src_onecell_get, clk_data);
+
+	return r;
+}
+
+static const struct of_device_id of_match_clk_mt2701_mm[] = {
+	{ .compatible = "mediatek,mt2701-mmsys", },
+	{}
+};
+
+static int clk_mt2701_mm_probe(struct platform_device *pdev)
+{
+	int r;
+
+	r = mtk_mmsys_init(pdev);
+	if (r) {
+		dev_err(&pdev->dev,
+			"could not register clock provider: %s: %d\n",
+			pdev->name, r);
+	}
+
+	return r;
+}
+
+static struct platform_driver clk_mt2701_mm_drv = {
+	.probe = clk_mt2701_mm_probe,
+	.driver = {
+		.name = "clk-mt2701-mm",
+		.of_match_table = of_match_clk_mt2701_mm,
+	},
+};
+
+builtin_platform_driver(clk_mt2701_mm_drv);
diff --git a/drivers/clk/mediatek/clk-mt2701-vdec.c b/drivers/clk/mediatek/clk-mt2701-vdec.c
new file mode 100644
index 0000000..405e25d
--- /dev/null
+++ b/drivers/clk/mediatek/clk-mt2701-vdec.c
@@ -0,0 +1,101 @@
+/*
+ * Copyright (c) 2014 MediaTek Inc.
+ * Author: Shunli Wang <shunli.wang@mediatek.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ */
+
+#include <linux/clk-provider.h>
+#include <linux/platform_device.h>
+
+#include "clk-mtk.h"
+#include "clk-gate.h"
+
+#include <dt-bindings/clock/mt2701-clk.h>
+
+static const struct mtk_gate_regs vdec0_cg_regs = {
+	.set_ofs = 0x0000,
+	.clr_ofs = 0x0004,
+	.sta_ofs = 0x0000,
+};
+
+static const struct mtk_gate_regs vdec1_cg_regs = {
+	.set_ofs = 0x0008,
+	.clr_ofs = 0x000c,
+	.sta_ofs = 0x0008,
+};
+
+#define GATE_VDEC0(_id, _name, _parent, _shift) {	\
+		.id = _id,				\
+		.name = _name,				\
+		.parent_name = _parent,			\
+		.regs = &vdec0_cg_regs,			\
+		.shift = _shift,			\
+		.ops = &mtk_clk_gate_ops_setclr_inv,	\
+	}
+
+#define GATE_VDEC1(_id, _name, _parent, _shift) {	\
+		.id = _id,				\
+		.name = _name,				\
+		.parent_name = _parent,			\
+		.regs = &vdec1_cg_regs,			\
+		.shift = _shift,			\
+		.ops = &mtk_clk_gate_ops_setclr_inv,	\
+	}
+
+static const struct mtk_gate vdec_clks[] = {
+	GATE_VDEC0(CLK_VDEC_CKGEN, "vdec_cken", "vdec_sel", 0),
+	GATE_VDEC1(CLK_VDEC_LARB, "vdec_larb_cken", "mm_sel", 0),
+};
+
+static int mtk_vdecsys_init(struct platform_device *pdev)
+{
+	struct clk_onecell_data *clk_data;
+	int r;
+	struct device_node *node = pdev->dev.of_node;
+
+	clk_data = mtk_alloc_clk_data(CLK_VDEC_NR);
+
+	mtk_clk_register_gates(node, vdec_clks, ARRAY_SIZE(vdec_clks),
+						clk_data);
+
+	r = of_clk_add_provider(node, of_clk_src_onecell_get, clk_data);
+
+	return r;
+}
+
+static const struct of_device_id of_match_clk_mt2701_vdec[] = {
+	{ .compatible = "mediatek,mt2701-vdecsys", },
+	{}
+};
+
+static int clk_mt2701_vdec_probe(struct platform_device *pdev)
+{
+	int r;
+
+	r = mtk_vdecsys_init(pdev);
+	if (r) {
+		dev_err(&pdev->dev,
+			"could not register clock provider: %s: %d\n",
+			pdev->name, r);
+	}
+
+	return r;
+}
+
+static struct platform_driver clk_mt2701_vdec_drv = {
+	.probe = clk_mt2701_vdec_probe,
+	.driver = {
+		.name = "clk-mt2701-vdec",
+		.of_match_table = of_match_clk_mt2701_vdec,
+	},
+};
+
+builtin_platform_driver(clk_mt2701_vdec_drv);
diff --git a/drivers/clk/mediatek/clk-mt2701.c b/drivers/clk/mediatek/clk-mt2701.c
new file mode 100644
index 0000000..c225256
--- /dev/null
+++ b/drivers/clk/mediatek/clk-mt2701.c
@@ -0,0 +1,1046 @@
+/*
+ * Copyright (c) 2014 MediaTek Inc.
+ * Author: Shunli Wang <shunli.wang@mediatek.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ */
+
+#include <linux/clk-provider.h>
+#include <linux/of.h>
+#include <linux/of_address.h>
+#include <linux/of_device.h>
+#include <linux/platform_device.h>
+
+#include "clk-mtk.h"
+#include "clk-gate.h"
+
+#include <dt-bindings/clock/mt2701-clk.h>
+
+/*
+ * For some clocks, we don't care what their actual rates are. And these
+ * clocks may change their rate on different products or different scenarios.
+ * So we model these clocks' rate as 0, to denote it's not an actual rate.
+ */
+#define DUMMY_RATE		0
+
+static DEFINE_SPINLOCK(lock);
+
+static const struct mtk_fixed_clk top_fixed_clks[] = {
+	FIXED_CLK(CLK_TOP_DPI, "dpi_ck", "clk26m",
+		108 * MHZ),
+	FIXED_CLK(CLK_TOP_DMPLL, "dmpll_ck", "clk26m",
+		400 * MHZ),
+	FIXED_CLK(CLK_TOP_VENCPLL, "vencpll_ck", "clk26m",
+		295750000),
+	FIXED_CLK(CLK_TOP_HDMI_0_PIX340M, "hdmi_0_pix340m", "clk26m",
+		340 * MHZ),
+	FIXED_CLK(CLK_TOP_HDMI_0_DEEP340M, "hdmi_0_deep340m", "clk26m",
+		340 * MHZ),
+	FIXED_CLK(CLK_TOP_HDMI_0_PLL340M, "hdmi_0_pll340m", "clk26m",
+		340 * MHZ),
+	FIXED_CLK(CLK_TOP_HDMITX_CLKDIG_CTS, "hdmitx_dig_cts", "clk26m",
+		300 * MHZ),
+	FIXED_CLK(CLK_TOP_HADDS2_FB, "hadds2_fbclk", "clk26m",
+		27 * MHZ),
+	FIXED_CLK(CLK_TOP_WBG_DIG_416M, "wbg_dig_ck_416m", "clk26m",
+		416 * MHZ),
+	FIXED_CLK(CLK_TOP_DSI0_LNTC_DSI, "dsi0_lntc_dsi", "clk26m",
+		143 * MHZ),
+	FIXED_CLK(CLK_TOP_HDMI_SCL_RX, "hdmi_scl_rx", "clk26m",
+		27 * MHZ),
+	FIXED_CLK(CLK_TOP_AUD_EXT1, "aud_ext1", "clk26m",
+		DUMMY_RATE),
+	FIXED_CLK(CLK_TOP_AUD_EXT2, "aud_ext2", "clk26m",
+		DUMMY_RATE),
+	FIXED_CLK(CLK_TOP_NFI1X_PAD, "nfi1x_pad", "clk26m",
+		DUMMY_RATE),
+};
+
+static const struct mtk_fixed_factor top_fixed_divs[] = {
+	FACTOR(CLK_TOP_SYSPLL, "syspll_ck", "mainpll", 1, 1),
+	FACTOR(CLK_TOP_SYSPLL_D2, "syspll_d2", "mainpll", 1, 2),
+	FACTOR(CLK_TOP_SYSPLL_D3, "syspll_d3", "mainpll", 1, 3),
+	FACTOR(CLK_TOP_SYSPLL_D5, "syspll_d5", "mainpll", 1, 5),
+	FACTOR(CLK_TOP_SYSPLL_D7, "syspll_d7", "mainpll", 1, 7),
+	FACTOR(CLK_TOP_SYSPLL1_D2, "syspll1_d2", "syspll_d2", 1, 2),
+	FACTOR(CLK_TOP_SYSPLL1_D4, "syspll1_d4", "syspll_d2", 1, 4),
+	FACTOR(CLK_TOP_SYSPLL1_D8, "syspll1_d8", "syspll_d2", 1, 8),
+	FACTOR(CLK_TOP_SYSPLL1_D16, "syspll1_d16", "syspll_d2", 1, 16),
+	FACTOR(CLK_TOP_SYSPLL2_D2, "syspll2_d2", "syspll_d3", 1, 2),
+	FACTOR(CLK_TOP_SYSPLL2_D4, "syspll2_d4", "syspll_d3", 1, 4),
+	FACTOR(CLK_TOP_SYSPLL2_D8, "syspll2_d8", "syspll_d3", 1, 8),
+	FACTOR(CLK_TOP_SYSPLL3_D2, "syspll3_d2", "syspll_d5", 1, 2),
+	FACTOR(CLK_TOP_SYSPLL3_D4, "syspll3_d4", "syspll_d5", 1, 4),
+	FACTOR(CLK_TOP_SYSPLL4_D2, "syspll4_d2", "syspll_d7", 1, 2),
+	FACTOR(CLK_TOP_SYSPLL4_D4, "syspll4_d4", "syspll_d7", 1, 4),
+
+	FACTOR(CLK_TOP_UNIVPLL, "univpll_ck", "univpll", 1, 1),
+	FACTOR(CLK_TOP_UNIVPLL_D2, "univpll_d2", "univpll", 1, 2),
+	FACTOR(CLK_TOP_UNIVPLL_D3, "univpll_d3", "univpll", 1, 3),
+	FACTOR(CLK_TOP_UNIVPLL_D5, "univpll_d5", "univpll", 1, 5),
+	FACTOR(CLK_TOP_UNIVPLL_D7, "univpll_d7", "univpll", 1, 7),
+	FACTOR(CLK_TOP_UNIVPLL_D26, "univpll_d26", "univpll", 1, 26),
+	FACTOR(CLK_TOP_UNIVPLL_D52, "univpll_d52", "univpll", 1, 52),
+	FACTOR(CLK_TOP_UNIVPLL_D108, "univpll_d108", "univpll", 1, 108),
+	FACTOR(CLK_TOP_USB_PHY48M, "usb_phy48m_ck", "univpll", 1, 26),
+	FACTOR(CLK_TOP_UNIVPLL1_D2, "univpll1_d2", "univpll_d2", 1, 2),
+	FACTOR(CLK_TOP_UNIVPLL1_D4, "univpll1_d4", "univpll_d2", 1, 4),
+	FACTOR(CLK_TOP_UNIVPLL1_D8, "univpll1_d8", "univpll_d2", 1, 8),
+	FACTOR(CLK_TOP_8BDAC, "8bdac_ck", "univpll_d2", 1, 1),
+	FACTOR(CLK_TOP_UNIVPLL2_D2, "univpll2_d2", "univpll_d3", 1, 2),
+	FACTOR(CLK_TOP_UNIVPLL2_D4, "univpll2_d4", "univpll_d3", 1, 4),
+	FACTOR(CLK_TOP_UNIVPLL2_D8, "univpll2_d8", "univpll_d3", 1, 8),
+	FACTOR(CLK_TOP_UNIVPLL2_D16, "univpll2_d16", "univpll_d3", 1, 16),
+	FACTOR(CLK_TOP_UNIVPLL2_D32, "univpll2_d32", "univpll_d3", 1, 32),
+	FACTOR(CLK_TOP_UNIVPLL3_D2, "univpll3_d2", "univpll_d5", 1, 2),
+	FACTOR(CLK_TOP_UNIVPLL3_D4, "univpll3_d4", "univpll_d5", 1, 4),
+	FACTOR(CLK_TOP_UNIVPLL3_D8, "univpll3_d8", "univpll_d5", 1, 8),
+
+	FACTOR(CLK_TOP_MSDCPLL, "msdcpll_ck", "msdcpll", 1, 1),
+	FACTOR(CLK_TOP_MSDCPLL_D2, "msdcpll_d2", "msdcpll", 1, 2),
+	FACTOR(CLK_TOP_MSDCPLL_D4, "msdcpll_d4", "msdcpll", 1, 4),
+	FACTOR(CLK_TOP_MSDCPLL_D8, "msdcpll_d8", "msdcpll", 1, 8),
+
+	FACTOR(CLK_TOP_MMPLL, "mmpll_ck", "mmpll", 1, 1),
+	FACTOR(CLK_TOP_MMPLL_D2, "mmpll_d2", "mmpll", 1, 2),
+
+	FACTOR(CLK_TOP_DMPLL_D2, "dmpll_d2", "dmpll_ck", 1, 2),
+	FACTOR(CLK_TOP_DMPLL_D4, "dmpll_d4", "dmpll_ck", 1, 4),
+	FACTOR(CLK_TOP_DMPLL_X2, "dmpll_x2", "dmpll_ck", 1, 1),
+
+	FACTOR(CLK_TOP_TVDPLL, "tvdpll_ck", "tvdpll", 1, 1),
+	FACTOR(CLK_TOP_TVDPLL_D2, "tvdpll_d2", "tvdpll", 1, 2),
+	FACTOR(CLK_TOP_TVDPLL_D4, "tvdpll_d4", "tvdpll", 1, 4),
+
+	FACTOR(CLK_TOP_VDECPLL, "vdecpll_ck", "vdecpll", 1, 1),
+	FACTOR(CLK_TOP_TVD2PLL, "tvd2pll_ck", "tvd2pll", 1, 1),
+	FACTOR(CLK_TOP_TVD2PLL_D2, "tvd2pll_d2", "tvd2pll", 1, 2),
+
+	FACTOR(CLK_TOP_MIPIPLL, "mipipll", "dpi_ck", 1, 1),
+	FACTOR(CLK_TOP_MIPIPLL_D2, "mipipll_d2", "dpi_ck", 1, 2),
+	FACTOR(CLK_TOP_MIPIPLL_D4, "mipipll_d4", "dpi_ck", 1, 4),
+
+	FACTOR(CLK_TOP_HDMIPLL, "hdmipll_ck", "hdmitx_dig_cts", 1, 1),
+	FACTOR(CLK_TOP_HDMIPLL_D2, "hdmipll_d2", "hdmitx_dig_cts", 1, 2),
+	FACTOR(CLK_TOP_HDMIPLL_D3, "hdmipll_d3", "hdmitx_dig_cts", 1, 3),
+
+	FACTOR(CLK_TOP_ARMPLL_1P3G, "armpll_1p3g_ck", "armpll", 1, 1),
+
+	FACTOR(CLK_TOP_AUDPLL, "audpll", "audpll_sel", 1, 1),
+	FACTOR(CLK_TOP_AUDPLL_D4, "audpll_d4", "audpll_sel", 1, 4),
+	FACTOR(CLK_TOP_AUDPLL_D8, "audpll_d8", "audpll_sel", 1, 8),
+	FACTOR(CLK_TOP_AUDPLL_D16, "audpll_d16", "audpll_sel", 1, 16),
+	FACTOR(CLK_TOP_AUDPLL_D24, "audpll_d24", "audpll_sel", 1, 24),
+
+	FACTOR(CLK_TOP_AUD1PLL_98M, "aud1pll_98m_ck", "aud1pll", 1, 3),
+	FACTOR(CLK_TOP_AUD2PLL_90M, "aud2pll_90m_ck", "aud2pll", 1, 3),
+	FACTOR(CLK_TOP_HADDS2PLL_98M, "hadds2pll_98m", "hadds2pll", 1, 3),
+	FACTOR(CLK_TOP_HADDS2PLL_294M, "hadds2pll_294m", "hadds2pll", 1, 1),
+	FACTOR(CLK_TOP_ETHPLL_500M, "ethpll_500m_ck", "ethpll", 1, 1),
+	FACTOR(CLK_TOP_CLK26M_D8, "clk26m_d8", "clk26m", 1, 8),
+	FACTOR(CLK_TOP_32K_INTERNAL, "32k_internal", "clk26m", 1, 793),
+	FACTOR(CLK_TOP_32K_EXTERNAL, "32k_external", "rtc32k", 1, 1),
+};
+
+static const char * const axi_parents[] = {
+	"clk26m",
+	"syspll1_d2",
+	"syspll_d5",
+	"syspll1_d4",
+	"univpll_d5",
+	"univpll2_d2",
+	"mmpll_d2",
+	"dmpll_d2"
+};
+
+static const char * const mem_parents[] = {
+	"clk26m",
+	"dmpll_ck"
+};
+
+static const char * const ddrphycfg_parents[] = {
+	"clk26m",
+	"syspll1_d8"
+};
+
+static const char * const mm_parents[] = {
+	"clk26m",
+	"vencpll_ck",
+	"syspll1_d2",
+	"syspll1_d4",
+	"univpll_d5",
+	"univpll1_d2",
+	"univpll2_d2",
+	"dmpll_ck"
+};
+
+static const char * const pwm_parents[] = {
+	"clk26m",
+	"univpll2_d4",
+	"univpll3_d2",
+	"univpll1_d4",
+};
+
+static const char * const vdec_parents[] = {
+	"clk26m",
+	"vdecpll_ck",
+	"syspll_d5",
+	"syspll1_d4",
+	"univpll_d5",
+	"univpll2_d2",
+	"vencpll_ck",
+	"msdcpll_d2",
+	"mmpll_d2"
+};
+
+static const char * const mfg_parents[] = {
+	"clk26m",
+	"mmpll_ck",
+	"dmpll_x2_ck",
+	"msdcpll_ck",
+	"clk26m",
+	"syspll_d3",
+	"univpll_d3",
+	"univpll1_d2"
+};
+
+static const char * const camtg_parents[] = {
+	"clk26m",
+	"univpll_d26",
+	"univpll2_d2",
+	"syspll3_d2",
+	"syspll3_d4",
+	"msdcpll_d2",
+	"mmpll_d2"
+};
+
+static const char * const uart_parents[] = {
+	"clk26m",
+	"univpll2_d8"
+};
+
+static const char * const spi_parents[] = {
+	"clk26m",
+	"syspll3_d2",
+	"syspll4_d2",
+	"univpll2_d4",
+	"univpll1_d8"
+};
+
+static const char * const usb20_parents[] = {
+	"clk26m",
+	"univpll1_d8",
+	"univpll3_d4"
+};
+
+static const char * const msdc30_parents[] = {
+	"clk26m",
+	"msdcpll_d2",
+	"syspll2_d2",
+	"syspll1_d4",
+	"univpll1_d4",
+	"univpll2_d4"
+};
+
+static const char * const audio_parents[] = {
+	"clk26m",
+	"syspll1_d16"
+};
+
+static const char * const aud_intbus_parents[] = {
+	"clk26m",
+	"syspll1_d4",
+	"syspll3_d2",
+	"syspll4_d2",
+	"univpll3_d2",
+	"univpll2_d4"
+};
+
+static const char * const pmicspi_parents[] = {
+	"clk26m",
+	"syspll1_d8",
+	"syspll2_d4",
+	"syspll4_d2",
+	"syspll3_d4",
+	"syspll2_d8",
+	"syspll1_d16",
+	"univpll3_d4",
+	"univpll_d26",
+	"dmpll_d2",
+	"dmpll_d4"
+};
+
+static const char * const scp_parents[] = {
+	"clk26m",
+	"syspll1_d8",
+	"dmpll_d2",
+	"dmpll_d4"
+};
+
+static const char * const dpi0_parents[] = {
+	"clk26m",
+	"mipipll",
+	"mipipll_d2",
+	"mipipll_d4",
+	"clk26m",
+	"tvdpll_ck",
+	"tvdpll_d2",
+	"tvdpll_d4"
+};
+
+static const char * const dpi1_parents[] = {
+	"clk26m",
+	"tvdpll_ck",
+	"tvdpll_d2",
+	"tvdpll_d4"
+};
+
+static const char * const tve_parents[] = {
+	"clk26m",
+	"mipipll",
+	"mipipll_d2",
+	"mipipll_d4",
+	"clk26m",
+	"tvdpll_ck",
+	"tvdpll_d2",
+	"tvdpll_d4"
+};
+
+static const char * const hdmi_parents[] = {
+	"clk26m",
+	"hdmipll_ck",
+	"hdmipll_d2",
+	"hdmipll_d3"
+};
+
+static const char * const apll_parents[] = {
+	"clk26m",
+	"audpll",
+	"audpll_d4",
+	"audpll_d8",
+	"audpll_d16",
+	"audpll_d24",
+	"clk26m",
+	"clk26m"
+};
+
+static const char * const rtc_parents[] = {
+	"32k_internal",
+	"32k_external",
+	"clk26m",
+	"univpll3_d8"
+};
+
+static const char * const nfi2x_parents[] = {
+	"clk26m",
+	"syspll2_d2",
+	"syspll_d7",
+	"univpll3_d2",
+	"syspll2_d4",
+	"univpll3_d4",
+	"syspll4_d4",
+	"clk26m"
+};
+
+static const char * const emmc_hclk_parents[] = {
+	"clk26m",
+	"syspll1_d2",
+	"syspll1_d4",
+	"syspll2_d2"
+};
+
+static const char * const flash_parents[] = {
+	"clk26m_d8",
+	"clk26m",
+	"syspll2_d8",
+	"syspll3_d4",
+	"univpll3_d4",
+	"syspll4_d2",
+	"syspll2_d4",
+	"univpll2_d4"
+};
+
+static const char * const di_parents[] = {
+	"clk26m",
+	"tvd2pll_ck",
+	"tvd2pll_d2",
+	"clk26m"
+};
+
+static const char * const nr_osd_parents[] = {
+	"clk26m",
+	"vencpll_ck",
+	"syspll1_d2",
+	"syspll1_d4",
+	"univpll_d5",
+	"univpll1_d2",
+	"univpll2_d2",
+	"dmpll_ck"
+};
+
+static const char * const hdmirx_bist_parents[] = {
+	"clk26m",
+	"syspll_d3",
+	"clk26m",
+	"syspll1_d16",
+	"syspll4_d2",
+	"syspll1_d4",
+	"vencpll_ck",
+	"clk26m"
+};
+
+static const char * const intdir_parents[] = {
+	"clk26m",
+	"mmpll_ck",
+	"syspll_d2",
+	"univpll_d2"
+};
+
+static const char * const asm_parents[] = {
+	"clk26m",
+	"univpll2_d4",
+	"univpll2_d2",
+	"syspll_d5"
+};
+
+static const char * const ms_card_parents[] = {
+	"clk26m",
+	"univpll3_d8",
+	"syspll4_d4"
+};
+
+static const char * const ethif_parents[] = {
+	"clk26m",
+	"syspll1_d2",
+	"syspll_d5",
+	"syspll1_d4",
+	"univpll_d5",
+	"univpll1_d2",
+	"dmpll_ck",
+	"dmpll_d2"
+};
+
+static const char * const hdmirx_parents[] = {
+	"clk26m",
+	"univpll_d52"
+};
+
+static const char * const cmsys_parents[] = {
+	"clk26m",
+	"syspll1_d2",
+	"univpll1_d2",
+	"univpll_d5",
+	"syspll_d5",
+	"syspll2_d2",
+	"syspll1_d4",
+	"syspll3_d2",
+	"syspll2_d4",
+	"syspll1_d8",
+	"clk26m",
+	"clk26m",
+	"clk26m",
+	"clk26m",
+	"clk26m"
+};
+
+static const char * const clk_8bdac_parents[] = {
+	"32k_internal",
+	"8bdac_ck",
+	"clk26m",
+	"clk26m"
+};
+
+static const char * const aud2dvd_parents[] = {
+	"a1sys_hp_ck",
+	"a2sys_hp_ck"
+};
+
+static const char * const padmclk_parents[] = {
+	"clk26m",
+	"univpll_d26",
+	"univpll_d52",
+	"univpll_d108",
+	"univpll2_d8",
+	"univpll2_d16",
+	"univpll2_d32"
+};
+
+static const char * const aud_mux_parents[] = {
+	"clk26m",
+	"aud1pll_98m_ck",
+	"aud2pll_90m_ck",
+	"hadds2pll_98m",
+	"audio_ext1_ck",
+	"audio_ext2_ck"
+};
+
+static const char * const aud_src_parents[] = {
+	"aud_mux1_sel",
+	"aud_mux2_sel"
+};
+
+static const char * const cpu_parents[] = {
+	"clk26m",
+	"armpll",
+	"mainpll",
+	"mmpll"
+};
+
+static const struct mtk_composite top_muxes[] = {
+	MUX_GATE_FLAGS(CLK_TOP_AXI_SEL, "axi_sel", axi_parents,
+		0x0040, 0, 3, 7, CLK_IS_CRITICAL),
+	MUX_GATE_FLAGS(CLK_TOP_MEM_SEL, "mem_sel", mem_parents,
+		0x0040, 8, 1, 15, CLK_IS_CRITICAL),
+	MUX_GATE_FLAGS(CLK_TOP_DDRPHYCFG_SEL, "ddrphycfg_sel",
+		ddrphycfg_parents, 0x0040, 16, 1, 23, CLK_IS_CRITICAL),
+	MUX_GATE(CLK_TOP_MM_SEL, "mm_sel", mm_parents,
+		0x0040, 24, 3, 31),
+
+	MUX_GATE(CLK_TOP_PWM_SEL, "pwm_sel", pwm_parents,
+		0x0050, 0, 2, 7),
+	MUX_GATE(CLK_TOP_VDEC_SEL, "vdec_sel", vdec_parents,
+		0x0050, 8, 4, 15),
+	MUX_GATE(CLK_TOP_MFG_SEL, "mfg_sel", mfg_parents,
+		0x0050, 16, 3, 23),
+	MUX_GATE(CLK_TOP_CAMTG_SEL, "camtg_sel", camtg_parents,
+		0x0050, 24, 3, 31),
+	MUX_GATE(CLK_TOP_UART_SEL, "uart_sel", uart_parents,
+		0x0060, 0, 1, 7),
+
+	MUX_GATE(CLK_TOP_SPI0_SEL, "spi0_sel", spi_parents,
+		0x0060, 8, 3, 15),
+	MUX_GATE(CLK_TOP_USB20_SEL, "usb20_sel", usb20_parents,
+		0x0060, 16, 2, 23),
+	MUX_GATE(CLK_TOP_MSDC30_0_SEL, "msdc30_0_sel", msdc30_parents,
+		0x0060, 24, 3, 31),
+
+	MUX_GATE(CLK_TOP_MSDC30_1_SEL, "msdc30_1_sel", msdc30_parents,
+		0x0070, 0, 3, 7),
+	MUX_GATE(CLK_TOP_MSDC30_2_SEL, "msdc30_2_sel", msdc30_parents,
+		0x0070, 8, 3, 15),
+	MUX_GATE(CLK_TOP_AUDIO_SEL, "audio_sel", msdc30_parents,
+		0x0070, 16, 1, 23),
+	MUX_GATE(CLK_TOP_AUDINTBUS_SEL, "aud_intbus_sel", aud_intbus_parents,
+		0x0070, 24, 3, 31),
+
+	MUX_GATE(CLK_TOP_PMICSPI_SEL, "pmicspi_sel", pmicspi_parents,
+		0x0080, 0, 4, 7),
+	MUX_GATE(CLK_TOP_SCP_SEL, "scp_sel", scp_parents,
+		0x0080, 8, 2, 15),
+	MUX_GATE(CLK_TOP_DPI0_SEL, "dpi0_sel", dpi0_parents,
+		0x0080, 16, 3, 23),
+	MUX_GATE(CLK_TOP_DPI1_SEL, "dpi1_sel", dpi1_parents,
+		0x0080, 24, 2, 31),
+
+	MUX_GATE(CLK_TOP_TVE_SEL, "tve_sel", tve_parents,
+		0x0090, 0, 3, 7),
+	MUX_GATE(CLK_TOP_HDMI_SEL, "hdmi_sel", hdmi_parents,
+		0x0090, 8, 2, 15),
+	MUX_GATE(CLK_TOP_APLL_SEL, "apll_sel", apll_parents,
+		0x0090, 16, 3, 23),
+
+	MUX_GATE_FLAGS(CLK_TOP_RTC_SEL, "rtc_sel", rtc_parents,
+		0x00A0, 0, 2, 7, CLK_IS_CRITICAL),
+	MUX_GATE(CLK_TOP_NFI2X_SEL, "nfi2x_sel", nfi2x_parents,
+		0x00A0, 8, 3, 15),
+	MUX_GATE(CLK_TOP_EMMC_HCLK_SEL, "emmc_hclk_sel", emmc_hclk_parents,
+		0x00A0, 24, 2, 31),
+
+	MUX_GATE(CLK_TOP_FLASH_SEL, "flash_sel", flash_parents,
+		0x00B0, 0, 3, 7),
+	MUX_GATE(CLK_TOP_DI_SEL, "di_sel", di_parents,
+		0x00B0, 8, 2, 15),
+	MUX_GATE(CLK_TOP_NR_SEL, "nr_sel", nr_osd_parents,
+		0x00B0, 16, 3, 23),
+	MUX_GATE(CLK_TOP_OSD_SEL, "osd_sel", nr_osd_parents,
+		0x00B0, 24, 3, 31),
+
+	MUX_GATE(CLK_TOP_HDMIRX_BIST_SEL, "hdmirx_bist_sel",
+		hdmirx_bist_parents, 0x00C0, 0, 3, 7),
+	MUX_GATE(CLK_TOP_INTDIR_SEL, "intdir_sel", intdir_parents,
+		0x00C0, 8, 2, 15),
+	MUX_GATE(CLK_TOP_ASM_I_SEL, "asm_i_sel", asm_parents,
+		0x00C0, 16, 2, 23),
+	MUX_GATE(CLK_TOP_ASM_M_SEL, "asm_m_sel", asm_parents,
+		0x00C0, 24, 3, 31),
+
+	MUX_GATE(CLK_TOP_ASM_H_SEL, "asm_h_sel", asm_parents,
+		0x00D0, 0, 2, 7),
+	MUX_GATE(CLK_TOP_MS_CARD_SEL, "ms_card_sel", ms_card_parents,
+		0x00D0, 16, 2, 23),
+	MUX_GATE(CLK_TOP_ETHIF_SEL, "ethif_sel", ethif_parents,
+		0x00D0, 24, 3, 31),
+
+	MUX_GATE(CLK_TOP_HDMIRX26_24_SEL, "hdmirx26_24_sel", hdmirx_parents,
+		0x00E0, 0, 1, 7),
+	MUX_GATE(CLK_TOP_MSDC30_3_SEL, "msdc30_3_sel", msdc30_parents,
+		0x00E0, 8, 3, 15),
+	MUX_GATE(CLK_TOP_CMSYS_SEL, "cmsys_sel", cmsys_parents,
+		0x00E0, 16, 4, 23),
+
+	MUX_GATE(CLK_TOP_SPI1_SEL, "spi2_sel", spi_parents,
+		0x00E0, 24, 3, 31),
+	MUX_GATE(CLK_TOP_SPI2_SEL, "spi1_sel", spi_parents,
+		0x00F0, 0, 3, 7),
+	MUX_GATE(CLK_TOP_8BDAC_SEL, "8bdac_sel", clk_8bdac_parents,
+		0x00F0, 8, 2, 15),
+	MUX_GATE(CLK_TOP_AUD2DVD_SEL, "aud2dvd_sel", aud2dvd_parents,
+		0x00F0, 16, 1, 23),
+
+	MUX(CLK_TOP_PADMCLK_SEL, "padmclk_sel", padmclk_parents,
+		0x0100, 0, 3),
+
+	MUX(CLK_TOP_AUD_MUX1_SEL, "aud_mux1_sel", aud_mux_parents,
+		0x012c, 0, 3),
+	MUX(CLK_TOP_AUD_MUX2_SEL, "aud_mux2_sel", aud_mux_parents,
+		0x012c, 3, 3),
+	MUX(CLK_TOP_AUDPLL_MUX_SEL, "audpll_sel", aud_mux_parents,
+		0x012c, 6, 3),
+	MUX_GATE(CLK_TOP_AUD_K1_SRC_SEL, "aud_k1_src_sel", aud_src_parents,
+		0x012c, 15, 1, 23),
+	MUX_GATE(CLK_TOP_AUD_K2_SRC_SEL, "aud_k2_src_sel", aud_src_parents,
+		0x012c, 16, 1, 24),
+	MUX_GATE(CLK_TOP_AUD_K3_SRC_SEL, "aud_k3_src_sel", aud_src_parents,
+		0x012c, 17, 1, 25),
+	MUX_GATE(CLK_TOP_AUD_K4_SRC_SEL, "aud_k4_src_sel", aud_src_parents,
+		0x012c, 18, 1, 26),
+	MUX_GATE(CLK_TOP_AUD_K5_SRC_SEL, "aud_k5_src_sel", aud_src_parents,
+		0x012c, 19, 1, 27),
+	MUX_GATE(CLK_TOP_AUD_K6_SRC_SEL, "aud_k6_src_sel", aud_src_parents,
+		0x012c, 20, 1, 28),
+};
+
+static const struct mtk_clk_divider top_adj_divs[] = {
+	DIV_ADJ(CLK_TOP_AUD_EXTCK1_DIV, "audio_ext1_ck", "aud_ext1",
+		0x0120, 0, 8),
+	DIV_ADJ(CLK_TOP_AUD_EXTCK2_DIV, "audio_ext2_ck", "aud_ext2",
+		0x0120, 8, 8),
+	DIV_ADJ(CLK_TOP_AUD_MUX1_DIV, "aud_mux1_div", "aud_mux1_sel",
+		0x0120, 16, 8),
+	DIV_ADJ(CLK_TOP_AUD_MUX2_DIV, "aud_mux2_div", "aud_mux2_sel",
+		0x0120, 24, 8),
+	DIV_ADJ(CLK_TOP_AUD_K1_SRC_DIV, "aud_k1_src_div", "aud_k1_src_sel",
+		0x0124, 0, 8),
+	DIV_ADJ(CLK_TOP_AUD_K2_SRC_DIV, "aud_k2_src_div", "aud_k2_src_sel",
+		0x0124, 8, 8),
+	DIV_ADJ(CLK_TOP_AUD_K3_SRC_DIV, "aud_k3_src_div", "aud_k3_src_sel",
+		0x0124, 16, 8),
+	DIV_ADJ(CLK_TOP_AUD_K4_SRC_DIV, "aud_k4_src_div", "aud_k4_src_sel",
+		0x0124, 24, 8),
+	DIV_ADJ(CLK_TOP_AUD_K5_SRC_DIV, "aud_k5_src_div", "aud_k5_src_sel",
+		0x0128, 0, 8),
+	DIV_ADJ(CLK_TOP_AUD_K6_SRC_DIV, "aud_k6_src_div", "aud_k6_src_sel",
+		0x0128, 8, 8),
+};
+
+static const struct mtk_gate_regs top_aud_cg_regs = {
+	.sta_ofs = 0x012C,
+};
+
+#define GATE_TOP_AUD(_id, _name, _parent, _shift) {	\
+		.id = _id,				\
+		.name = _name,				\
+		.parent_name = _parent,			\
+		.regs = &top_aud_cg_regs,		\
+		.shift = _shift,			\
+		.ops = &mtk_clk_gate_ops_no_setclr,	\
+	}
+
+static const struct mtk_gate top_clks[] = {
+	GATE_TOP_AUD(CLK_TOP_AUD_48K_TIMING, "a1sys_hp_ck", "aud_mux1_div",
+		21),
+	GATE_TOP_AUD(CLK_TOP_AUD_44K_TIMING, "a2sys_hp_ck", "aud_mux2_div",
+		22),
+	GATE_TOP_AUD(CLK_TOP_AUD_I2S1_MCLK, "aud_i2s1_mclk", "aud_k1_src_div",
+		23),
+	GATE_TOP_AUD(CLK_TOP_AUD_I2S2_MCLK, "aud_i2s2_mclk", "aud_k2_src_div",
+		24),
+	GATE_TOP_AUD(CLK_TOP_AUD_I2S3_MCLK, "aud_i2s3_mclk", "aud_k3_src_div",
+		25),
+	GATE_TOP_AUD(CLK_TOP_AUD_I2S4_MCLK, "aud_i2s4_mclk", "aud_k4_src_div",
+		26),
+	GATE_TOP_AUD(CLK_TOP_AUD_I2S5_MCLK, "aud_i2s5_mclk", "aud_k5_src_div",
+		27),
+	GATE_TOP_AUD(CLK_TOP_AUD_I2S6_MCLK, "aud_i2s6_mclk", "aud_k6_src_div",
+		28),
+};
+
+void __iomem *devm_of_iomap(struct device *dev, int index)
+{
+	struct resource res;
+
+	if (!dev)
+		return NULL;
+
+	if (of_address_to_resource(dev->of_node, index, &res))
+		return NULL;
+
+	return devm_ioremap(dev, res.start, resource_size(&res));
+}
+
+static int mtk_topckgen_init(struct platform_device *pdev)
+{
+	struct clk_onecell_data *clk_data;
+	void __iomem *base;
+	int r;
+	struct device_node *node = pdev->dev.of_node;
+
+	base = devm_of_iomap(&pdev->dev, 0);
+	if (!base)
+		return -ENOMEM;
+
+	clk_data = mtk_alloc_clk_data(CLK_TOP_NR);
+
+	mtk_clk_register_fixed_clks(top_fixed_clks, ARRAY_SIZE(top_fixed_clks),
+								clk_data);
+
+	mtk_clk_register_factors(top_fixed_divs, ARRAY_SIZE(top_fixed_divs),
+								clk_data);
+
+	mtk_clk_register_composites(top_muxes, ARRAY_SIZE(top_muxes),
+				base, &lock, clk_data);
+
+	mtk_clk_register_dividers(top_adj_divs, ARRAY_SIZE(top_adj_divs),
+				base, &lock, clk_data);
+
+	mtk_clk_register_gates(node, top_clks, ARRAY_SIZE(top_clks),
+						clk_data);
+
+	r = of_clk_add_provider(node, of_clk_src_onecell_get, clk_data);
+
+	return r;
+}
+
+static const struct mtk_gate_regs infra_cg_regs = {
+	.set_ofs = 0x0040,
+	.clr_ofs = 0x0044,
+	.sta_ofs = 0x0048,
+};
+
+#define GATE_ICG(_id, _name, _parent, _shift) {		\
+		.id = _id,				\
+		.name = _name,				\
+		.parent_name = _parent,			\
+		.regs = &infra_cg_regs,			\
+		.shift = _shift,			\
+		.ops = &mtk_clk_gate_ops_setclr,	\
+	}
+
+static const struct mtk_gate infra_clks[] = {
+	GATE_ICG(CLK_INFRA_DBG, "dbgclk", "axi_sel", 0),
+	GATE_ICG(CLK_INFRA_SMI, "smi_ck", "mm_sel", 1),
+	GATE_ICG(CLK_INFRA_QAXI_CM4, "cm4_ck", "axi_sel", 2),
+	GATE_ICG(CLK_INFRA_AUD_SPLIN_B, "audio_splin_bck", "hadds2pll_294m", 4),
+	GATE_ICG(CLK_INFRA_AUDIO, "audio_ck", "clk26m", 5),
+	GATE_ICG(CLK_INFRA_EFUSE, "efuse_ck", "clk26m", 6),
+	GATE_ICG(CLK_INFRA_L2C_SRAM, "l2c_sram_ck", "mm_sel", 7),
+	GATE_ICG(CLK_INFRA_M4U, "m4u_ck", "mem_sel", 8),
+	GATE_ICG(CLK_INFRA_CONNMCU, "connsys_bus", "wbg_dig_ck_416m", 12),
+	GATE_ICG(CLK_INFRA_TRNG, "trng_ck", "axi_sel", 13),
+	GATE_ICG(CLK_INFRA_RAMBUFIF, "rambufif_ck", "mem_sel", 14),
+	GATE_ICG(CLK_INFRA_CPUM, "cpum_ck", "mem_sel", 15),
+	GATE_ICG(CLK_INFRA_KP, "kp_ck", "axi_sel", 16),
+	GATE_ICG(CLK_INFRA_CEC, "cec_ck", "rtc_sel", 18),
+	GATE_ICG(CLK_INFRA_IRRX, "irrx_ck", "axi_sel", 19),
+	GATE_ICG(CLK_INFRA_PMICSPI, "pmicspi_ck", "pmicspi_sel", 22),
+	GATE_ICG(CLK_INFRA_PMICWRAP, "pmicwrap_ck", "axi_sel", 23),
+	GATE_ICG(CLK_INFRA_DDCCI, "ddcci_ck", "axi_sel", 24),
+};
+
+static const struct mtk_fixed_factor infra_fixed_divs[] = {
+	FACTOR(CLK_INFRA_CLK_13M, "clk13m", "clk26m", 1, 2),
+};
+
+static struct clk_onecell_data *infra_clk_data;
+
+static void mtk_infrasys_init_early(struct device_node *node)
+{
+	int r, i;
+
+	if (!infra_clk_data) {
+		infra_clk_data = mtk_alloc_clk_data(CLK_INFRA_NR);
+
+		for (i = 0; i < CLK_INFRA_NR; i++)
+			infra_clk_data->clks[i] = ERR_PTR(-EPROBE_DEFER);
+	}
+
+	mtk_clk_register_factors(infra_fixed_divs, ARRAY_SIZE(infra_fixed_divs),
+						infra_clk_data);
+
+	r = of_clk_add_provider(node, of_clk_src_onecell_get, infra_clk_data);
+	if (r)
+		pr_err("%s(): could not register clock provider: %d\n",
+			__func__, r);
+}
+CLK_OF_DECLARE_DRIVER(mtk_infra, "mediatek,mt2701-infracfg",
+			mtk_infrasys_init_early);
+
+static int mtk_infrasys_init(struct platform_device *pdev)
+{
+	int r, i;
+	struct device_node *node = pdev->dev.of_node;
+
+	if (!infra_clk_data) {
+		infra_clk_data = mtk_alloc_clk_data(CLK_INFRA_NR);
+	} else {
+		for (i = 0; i < CLK_INFRA_NR; i++) {
+			if (infra_clk_data->clks[i] == ERR_PTR(-EPROBE_DEFER))
+				infra_clk_data->clks[i] = ERR_PTR(-ENOENT);
+		}
+	}
+
+	mtk_clk_register_gates(node, infra_clks, ARRAY_SIZE(infra_clks),
+						infra_clk_data);
+	mtk_clk_register_factors(infra_fixed_divs, ARRAY_SIZE(infra_fixed_divs),
+						infra_clk_data);
+
+	r = of_clk_add_provider(node, of_clk_src_onecell_get, infra_clk_data);
+
+	return r;
+}
+
+static const struct mtk_gate_regs peri0_cg_regs = {
+	.set_ofs = 0x0008,
+	.clr_ofs = 0x0010,
+	.sta_ofs = 0x0018,
+};
+
+static const struct mtk_gate_regs peri1_cg_regs = {
+	.set_ofs = 0x000c,
+	.clr_ofs = 0x0014,
+	.sta_ofs = 0x001c,
+};
+
+#define GATE_PERI0(_id, _name, _parent, _shift) {	\
+		.id = _id,				\
+		.name = _name,				\
+		.parent_name = _parent,			\
+		.regs = &peri0_cg_regs,			\
+		.shift = _shift,			\
+		.ops = &mtk_clk_gate_ops_setclr,	\
+	}
+
+#define GATE_PERI1(_id, _name, _parent, _shift) {	\
+		.id = _id,				\
+		.name = _name,				\
+		.parent_name = _parent,			\
+		.regs = &peri1_cg_regs,			\
+		.shift = _shift,			\
+		.ops = &mtk_clk_gate_ops_setclr,	\
+	}
+
+static const struct mtk_gate peri_clks[] = {
+	GATE_PERI0(CLK_PERI_USB0_MCU, "usb0_mcu_ck", "axi_sel", 31),
+	GATE_PERI0(CLK_PERI_ETH, "eth_ck", "clk26m", 30),
+	GATE_PERI0(CLK_PERI_SPI0, "spi0_ck", "spi0_sel", 29),
+	GATE_PERI0(CLK_PERI_AUXADC, "auxadc_ck", "clk26m", 28),
+	GATE_PERI0(CLK_PERI_I2C3, "i2c3_ck", "clk26m", 27),
+	GATE_PERI0(CLK_PERI_I2C2, "i2c2_ck", "axi_sel", 26),
+	GATE_PERI0(CLK_PERI_I2C1, "i2c1_ck", "axi_sel", 25),
+	GATE_PERI0(CLK_PERI_I2C0, "i2c0_ck", "axi_sel", 24),
+	GATE_PERI0(CLK_PERI_BTIF, "bitif_ck", "axi_sel", 23),
+	GATE_PERI0(CLK_PERI_UART3, "uart3_ck", "axi_sel", 22),
+	GATE_PERI0(CLK_PERI_UART2, "uart2_ck", "axi_sel", 21),
+	GATE_PERI0(CLK_PERI_UART1, "uart1_ck", "axi_sel", 20),
+	GATE_PERI0(CLK_PERI_UART0, "uart0_ck", "axi_sel", 19),
+	GATE_PERI0(CLK_PERI_NLI, "nli_ck", "axi_sel", 18),
+	GATE_PERI0(CLK_PERI_MSDC50_3, "msdc50_3_ck", "emmc_hclk_sel", 17),
+	GATE_PERI0(CLK_PERI_MSDC30_3, "msdc30_3_ck", "msdc30_3_sel", 16),
+	GATE_PERI0(CLK_PERI_MSDC30_2, "msdc30_2_ck", "msdc30_2_sel", 15),
+	GATE_PERI0(CLK_PERI_MSDC30_1, "msdc30_1_ck", "msdc30_1_sel", 14),
+	GATE_PERI0(CLK_PERI_MSDC30_0, "msdc30_0_ck", "msdc30_0_sel", 13),
+	GATE_PERI0(CLK_PERI_AP_DMA, "ap_dma_ck", "axi_sel", 12),
+	GATE_PERI0(CLK_PERI_USB1, "usb1_ck", "usb20_sel", 11),
+	GATE_PERI0(CLK_PERI_USB0, "usb0_ck", "usb20_sel", 10),
+	GATE_PERI0(CLK_PERI_PWM, "pwm_ck", "axi_sel", 9),
+	GATE_PERI0(CLK_PERI_PWM7, "pwm7_ck", "axi_sel", 8),
+	GATE_PERI0(CLK_PERI_PWM6, "pwm6_ck", "axi_sel", 7),
+	GATE_PERI0(CLK_PERI_PWM5, "pwm5_ck", "axi_sel", 6),
+	GATE_PERI0(CLK_PERI_PWM4, "pwm4_ck", "axi_sel", 5),
+	GATE_PERI0(CLK_PERI_PWM3, "pwm3_ck", "axi_sel", 4),
+	GATE_PERI0(CLK_PERI_PWM2, "pwm2_ck", "axi_sel", 3),
+	GATE_PERI0(CLK_PERI_PWM1, "pwm1_ck", "axi_sel", 2),
+	GATE_PERI0(CLK_PERI_THERM, "therm_ck", "axi_sel", 1),
+	GATE_PERI0(CLK_PERI_NFI, "nfi_ck", "nfi2x_sel", 0),
+
+	GATE_PERI1(CLK_PERI_FCI, "fci_ck", "ms_card_sel", 11),
+	GATE_PERI1(CLK_PERI_SPI2, "spi2_ck", "spi2_sel", 10),
+	GATE_PERI1(CLK_PERI_SPI1, "spi1_ck", "spi1_sel", 9),
+	GATE_PERI1(CLK_PERI_HOST89_DVD, "host89_dvd_ck", "aud2dvd_sel", 8),
+	GATE_PERI1(CLK_PERI_HOST89_SPI, "host89_spi_ck", "spi0_sel", 7),
+	GATE_PERI1(CLK_PERI_HOST89_INT, "host89_int_ck", "axi_sel", 6),
+	GATE_PERI1(CLK_PERI_FLASH, "flash_ck", "nfi2x_sel", 5),
+	GATE_PERI1(CLK_PERI_NFI_PAD, "nfi_pad_ck", "nfi1x_pad", 4),
+	GATE_PERI1(CLK_PERI_NFI_ECC, "nfi_ecc_ck", "nfi1x_pad", 3),
+	GATE_PERI1(CLK_PERI_GCPU, "gcpu_ck", "axi_sel", 2),
+	GATE_PERI1(CLK_PERI_USB_SLV, "usbslv_ck", "axi_sel", 1),
+	GATE_PERI1(CLK_PERI_USB1_MCU, "usb1_mcu_ck", "axi_sel", 0),
+};
+
+static const char * const uart_ck_sel_parents[] = {
+	"clk26m",
+	"uart_sel",
+};
+
+static const struct mtk_composite peri_muxs[] = {
+	MUX(CLK_PERI_UART0_SEL, "uart0_ck_sel", uart_ck_sel_parents,
+		0x40c, 0, 1),
+	MUX(CLK_PERI_UART1_SEL, "uart1_ck_sel", uart_ck_sel_parents,
+		0x40c, 1, 1),
+	MUX(CLK_PERI_UART2_SEL, "uart2_ck_sel", uart_ck_sel_parents,
+		0x40c, 2, 1),
+	MUX(CLK_PERI_UART3_SEL, "uart3_ck_sel", uart_ck_sel_parents,
+		0x40c, 3, 1),
+};
+
+static int mtk_pericfg_init(struct platform_device *pdev)
+{
+	struct clk_onecell_data *clk_data;
+	void __iomem *base;
+	int r;
+	struct device_node *node = pdev->dev.of_node;
+
+	base = devm_of_iomap(&pdev->dev, 0);
+	if (!base)
+		return -ENOMEM;
+
+	clk_data = mtk_alloc_clk_data(CLK_PERI_NR);
+
+	mtk_clk_register_gates(node, peri_clks, ARRAY_SIZE(peri_clks),
+						clk_data);
+
+	mtk_clk_register_composites(peri_muxs, ARRAY_SIZE(peri_muxs), base,
+			&lock, clk_data);
+
+	r = of_clk_add_provider(node, of_clk_src_onecell_get, clk_data);
+
+	return r;
+}
+
+#define MT8590_PLL_FMAX		(2000 * MHZ)
+#define CON0_MT8590_RST_BAR	BIT(27)
+
+#define PLL(_id, _name, _reg, _pwr_reg, _en_mask, _flags, _pcwbits, _pd_reg, \
+			_pd_shift, _tuner_reg, _pcw_reg, _pcw_shift) {	\
+		.id = _id,						\
+		.name = _name,						\
+		.reg = _reg,						\
+		.pwr_reg = _pwr_reg,					\
+		.en_mask = _en_mask,					\
+		.flags = _flags,					\
+		.rst_bar_mask = CON0_MT8590_RST_BAR,			\
+		.fmax = MT8590_PLL_FMAX,				\
+		.pcwbits = _pcwbits,					\
+		.pd_reg = _pd_reg,					\
+		.pd_shift = _pd_shift,					\
+		.tuner_reg = _tuner_reg,				\
+		.pcw_reg = _pcw_reg,					\
+		.pcw_shift = _pcw_shift,				\
+	}
+
+static const struct mtk_pll_data apmixed_plls[] = {
+	PLL(CLK_APMIXED_ARMPLL, "armpll", 0x200, 0x20c, 0x80000001,
+			PLL_AO, 21, 0x204, 24, 0x0, 0x204, 0),
+	PLL(CLK_APMIXED_MAINPLL, "mainpll", 0x210, 0x21c, 0xf0000001,
+		  HAVE_RST_BAR, 21, 0x210, 4, 0x0, 0x214, 0),
+	PLL(CLK_APMIXED_UNIVPLL, "univpll", 0x220, 0x22c, 0xf3000001,
+		  HAVE_RST_BAR, 7, 0x220, 4, 0x0, 0x224, 14),
+	PLL(CLK_APMIXED_MMPLL, "mmpll", 0x230, 0x23c, 0x00000001, 0,
+				21, 0x230, 4, 0x0, 0x234, 0),
+	PLL(CLK_APMIXED_MSDCPLL, "msdcpll", 0x240, 0x24c, 0x00000001, 0,
+				21, 0x240, 4, 0x0, 0x244, 0),
+	PLL(CLK_APMIXED_TVDPLL, "tvdpll", 0x250, 0x25c, 0x00000001, 0,
+				21, 0x250, 4, 0x0, 0x254, 0),
+	PLL(CLK_APMIXED_AUD1PLL, "aud1pll", 0x270, 0x27c, 0x00000001, 0,
+				31, 0x270, 4, 0x0, 0x274, 0),
+	PLL(CLK_APMIXED_TRGPLL, "trgpll", 0x280, 0x28c, 0x00000001, 0,
+				31, 0x280, 4, 0x0, 0x284, 0),
+	PLL(CLK_APMIXED_ETHPLL, "ethpll", 0x290, 0x29c, 0x00000001, 0,
+				31, 0x290, 4, 0x0, 0x294, 0),
+	PLL(CLK_APMIXED_VDECPLL, "vdecpll", 0x2a0, 0x2ac, 0x00000001, 0,
+				31, 0x2a0, 4, 0x0, 0x2a4, 0),
+	PLL(CLK_APMIXED_HADDS2PLL, "hadds2pll", 0x2b0, 0x2bc, 0x00000001, 0,
+				31, 0x2b0, 4, 0x0, 0x2b4, 0),
+	PLL(CLK_APMIXED_AUD2PLL, "aud2pll", 0x2c0, 0x2cc, 0x00000001, 0,
+				31, 0x2c0, 4, 0x0, 0x2c4, 0),
+	PLL(CLK_APMIXED_TVD2PLL, "tvd2pll", 0x2d0, 0x2dc, 0x00000001, 0,
+				21, 0x2d0, 4, 0x0, 0x2d4, 0),
+};
+
+static int mtk_apmixedsys_init(struct platform_device *pdev)
+{
+	struct clk_onecell_data *clk_data;
+	int r;
+	struct device_node *node = pdev->dev.of_node;
+
+	clk_data = mtk_alloc_clk_data(CLK_APMIXED_NR);
+	if (!clk_data)
+		return -ENOMEM;
+
+	mtk_clk_register_plls(node, apmixed_plls, ARRAY_SIZE(apmixed_plls),
+								clk_data);
+
+	r = of_clk_add_provider(node, of_clk_src_onecell_get, clk_data);
+
+	return r;
+}
+
+static const struct of_device_id of_match_clk_mt2701[] = {
+	{
+		.compatible = "mediatek,mt2701-topckgen",
+		.data = mtk_topckgen_init,
+	}, {
+		.compatible = "mediatek,mt2701-infracfg",
+		.data = mtk_infrasys_init,
+	}, {
+		.compatible = "mediatek,mt2701-pericfg",
+		.data = mtk_pericfg_init,
+	}, {
+		.compatible = "mediatek,mt2701-apmixedsys",
+		.data = mtk_apmixedsys_init,
+	}, {
+		/* sentinel */
+	}
+};
+
+static int clk_mt2701_probe(struct platform_device *pdev)
+{
+	int (*clk_init)(struct platform_device *);
+	int r;
+
+	clk_init = of_device_get_match_data(&pdev->dev);
+	if (!clk_init)
+		return -EINVAL;
+
+	r = clk_init(pdev);
+	if (r) {
+		dev_err(&pdev->dev,
+			"could not register clock provider: %s: %d\n",
+			pdev->name, r);
+	}
+
+	return r;
+}
+
+static struct platform_driver clk_mt2701_drv = {
+	.probe = clk_mt2701_probe,
+	.driver = {
+		.name = "clk-mt2701",
+		.owner = THIS_MODULE,
+		.of_match_table = of_match_clk_mt2701,
+	},
+};
+
+static int __init clk_mt2701_init(void)
+{
+	return platform_driver_register(&clk_mt2701_drv);
+}
+
+arch_initcall(clk_mt2701_init);
diff --git a/drivers/clk/mediatek/clk-mtk.c b/drivers/clk/mediatek/clk-mtk.c
index bb30f70..0541df7 100644
--- a/drivers/clk/mediatek/clk-mtk.c
+++ b/drivers/clk/mediatek/clk-mtk.c
@@ -58,6 +58,9 @@ void mtk_clk_register_fixed_clks(const struct mtk_fixed_clk *clks,
 	for (i = 0; i < num; i++) {
 		const struct mtk_fixed_clk *rc = &clks[i];
 
+		if (clk_data && !IS_ERR_OR_NULL(clk_data->clks[rc->id]))
+			continue;
+
 		clk = clk_register_fixed_rate(NULL, rc->name, rc->parent, 0,
 					      rc->rate);
 
@@ -81,6 +84,9 @@ void mtk_clk_register_factors(const struct mtk_fixed_factor *clks,
 	for (i = 0; i < num; i++) {
 		const struct mtk_fixed_factor *ff = &clks[i];
 
+		if (clk_data && !IS_ERR_OR_NULL(clk_data->clks[ff->id]))
+			continue;
+
 		clk = clk_register_fixed_factor(NULL, ff->name, ff->parent_name,
 				CLK_SET_RATE_PARENT, ff->mult, ff->div);
 
@@ -116,6 +122,9 @@ int mtk_clk_register_gates(struct device_node *node,
 	for (i = 0; i < num; i++) {
 		const struct mtk_gate *gate = &clks[i];
 
+		if (!IS_ERR_OR_NULL(clk_data->clks[gate->id]))
+			continue;
+
 		clk = mtk_clk_register_gate(gate->name, gate->parent_name,
 				regmap,
 				gate->regs->set_ofs,
@@ -232,6 +241,9 @@ void mtk_clk_register_composites(const struct mtk_composite *mcs,
 	for (i = 0; i < num; i++) {
 		const struct mtk_composite *mc = &mcs[i];
 
+		if (clk_data && !IS_ERR_OR_NULL(clk_data->clks[mc->id]))
+			continue;
+
 		clk = mtk_clk_register_composite(mc, base, lock);
 
 		if (IS_ERR(clk)) {
@@ -244,3 +256,31 @@ void mtk_clk_register_composites(const struct mtk_composite *mcs,
 			clk_data->clks[mc->id] = clk;
 	}
 }
+
+void mtk_clk_register_dividers(const struct mtk_clk_divider *mcds,
+			int num, void __iomem *base, spinlock_t *lock,
+				struct clk_onecell_data *clk_data)
+{
+	struct clk *clk;
+	int i;
+
+	for (i = 0; i <  num; i++) {
+		const struct mtk_clk_divider *mcd = &mcds[i];
+
+		if (clk_data && !IS_ERR_OR_NULL(clk_data->clks[mcd->id]))
+			continue;
+
+		clk = clk_register_divider(NULL, mcd->name, mcd->parent_name,
+			mcd->flags, base +  mcd->div_reg, mcd->div_shift,
+			mcd->div_width, mcd->clk_divider_flags, lock);
+
+		if (IS_ERR(clk)) {
+			pr_err("Failed to register clk %s: %ld\n",
+				mcd->name, PTR_ERR(clk));
+			continue;
+		}
+
+		if (clk_data)
+			clk_data->clks[mcd->id] = clk;
+	}
+}
diff --git a/drivers/clk/mediatek/clk-mtk.h b/drivers/clk/mediatek/clk-mtk.h
index 9f24fcf..f5d6b70 100644
--- a/drivers/clk/mediatek/clk-mtk.h
+++ b/drivers/clk/mediatek/clk-mtk.h
@@ -87,7 +87,8 @@ struct mtk_composite {
  * In case the rate change propagation to parent clocks is undesirable,
  * this macro allows to specify the clock flags manually.
  */
-#define MUX_GATE_FLAGS(_id, _name, _parents, _reg, _shift, _width, _gate, _flags) {	\
+#define MUX_GATE_FLAGS(_id, _name, _parents, _reg, _shift, _width,	\
+			_gate, _flags) {				\
 		.id = _id,						\
 		.name = _name,						\
 		.mux_reg = _reg,					\
@@ -106,7 +107,8 @@ struct mtk_composite {
  * parent clock by default.
  */
 #define MUX_GATE(_id, _name, _parents, _reg, _shift, _width, _gate)	\
-	MUX_GATE_FLAGS(_id, _name, _parents, _reg, _shift, _width, _gate, CLK_SET_RATE_PARENT)
+	MUX_GATE_FLAGS(_id, _name, _parents, _reg, _shift, _width,	\
+		_gate, CLK_SET_RATE_PARENT)
 
 #define MUX(_id, _name, _parents, _reg, _shift, _width) {		\
 		.id = _id,						\
@@ -121,7 +123,8 @@ struct mtk_composite {
 		.flags = CLK_SET_RATE_PARENT,				\
 	}
 
-#define DIV_GATE(_id, _name, _parent, _gate_reg, _gate_shift, _div_reg, _div_width, _div_shift) {	\
+#define DIV_GATE(_id, _name, _parent, _gate_reg, _gate_shift, _div_reg,	\
+					_div_width, _div_shift) {	\
 		.id = _id,						\
 		.parent = _parent,					\
 		.name = _name,						\
@@ -156,12 +159,40 @@ struct mtk_gate {
 	const struct clk_ops *ops;
 };
 
-int mtk_clk_register_gates(struct device_node *node, const struct mtk_gate *clks,
-		int num, struct clk_onecell_data *clk_data);
+int mtk_clk_register_gates(struct device_node *node,
+			const struct mtk_gate *clks, int num,
+			struct clk_onecell_data *clk_data);
+
+struct mtk_clk_divider {
+	int id;
+	const char *name;
+	const char *parent_name;
+	unsigned long flags;
+
+	u32 div_reg;
+	unsigned char div_shift;
+	unsigned char div_width;
+	unsigned char clk_divider_flags;
+	const struct clk_div_table *clk_div_table;
+};
+
+#define DIV_ADJ(_id, _name, _parent, _reg, _shift, _width) {	\
+		.id = _id,					\
+		.name = _name,					\
+		.parent_name = _parent,				\
+		.div_reg = _reg,				\
+		.div_shift = _shift,				\
+		.div_width = _width,				\
+}
+
+void mtk_clk_register_dividers(const struct mtk_clk_divider *mcds,
+			int num, void __iomem *base, spinlock_t *lock,
+				struct clk_onecell_data *clk_data);
 
 struct clk_onecell_data *mtk_alloc_clk_data(unsigned int clk_num);
 
 #define HAVE_RST_BAR	BIT(0)
+#define PLL_AO		BIT(1)
 
 struct mtk_pll_div_table {
 	u32 div;
diff --git a/drivers/clk/mediatek/clk-pll.c b/drivers/clk/mediatek/clk-pll.c
index 0c2deac..a409142 100644
--- a/drivers/clk/mediatek/clk-pll.c
+++ b/drivers/clk/mediatek/clk-pll.c
@@ -301,6 +301,7 @@ static struct clk *mtk_clk_register_pll(const struct mtk_pll_data *data,
 	pll->data = data;
 
 	init.name = data->name;
+	init.flags = (data->flags & PLL_AO) ? CLK_IS_CRITICAL : 0;
 	init.ops = &mtk_pll_ops;
 	init.parent_names = &parent_name;
 	init.num_parents = 1;
-- 
1.9.1

^ permalink raw reply related

* [PATCH v13 0/4] Add clock support for Mediatek MT2701
From: Erin Lo @ 2016-09-26  9:26 UTC (permalink / raw)
  To: linux-arm-kernel

This series is based on clk-next, add clock and reset controller support
for Mediatek MT2701.

Since some patches in v11 had been applied into clk-next, they were not
included in v13 series.

changes since v12:
- Rebase to clk-next.
- Use CLK_OF_DECLARE_DRIVER() instead of CLK_OF_DECLARE().
- Use dev_* and devm_* APIs instead of pr_* and ioremap().
- Refine init functions. Share error messages in common probe().
- Fix null pointer checking for clk_data.

changes since v11:
- Rebase to clk-next.
- Return error code from probe() if clock registration fail.

changes since v10:
- Remove COMMON_CLK dependency from clk/mediatek/Kconfig.

changes since v9:
- Rebase to v4.8-rc1.
- Drop a fix patch of parent clock initial state. It will be replaced by a new
  patch from Mike/Stephen.
- Replace clk.h with clk-provider.h.
- Correct register settings of clocks.

changes since v8:
- Rebase to v4.7-rc1.
- Include mt2701-resets.h in mt2701.dtsi.
- Remove an unused property from apmixedsys DT node.

changes since v7:
- Rebase to clk-next.
- Implement subsystem clocks in seperated files.
- Replace critical clock enabling with CLK_IS_CRITICAL flag.
- Reduce most clock registrations in CLK_OF_DECLARE().
- Remove __init and __initconst from most init fucntions and data,
  and replace driver registration with platform_driver_register().
- Replace some common function or variable names with unique names.
- Use real clock for UARTs.

changes since v6:
- Rebase to v4.6-rc1.
- Register subsystem clocks in probe() instead of CLK_OF_DECLARE().
- Add clocks that referred by subsystem clocks.
- Fix clk_data size of apmixedsys.
- Add config options for each subsystem clock provider.

changes since v5:
- Rebase to v4.5-rc1 and [1].
- Enable critical clocks for MT2701
- Refine dt-binding documents, add reset controller support for hifsys.

changes since v4:
- Rebase to v4.5-rc1.
- Remove CLK_SET_RATE_PARENT from divider flags.
- Add img_jpgdec_smi clock.
- Move clk/mediatek/Kconfig into menu section in clk/Kconfig.

changes since v3:
- Change the parent of mm_mdp_bls_26m from clk26m to pwm_sel.

changes since v2:
- Fix ethsys definition.
- Replace read-modify-write with regmap_update_bits() in clock operations.
- Move mt2701-resets.h to include/dt-bindings/reset/.
- Add hifsys reset patch from John Crispin.

changes since v1:
- Document MT2701 compatible strings.

[1] https://patchwork.kernel.org/patch/8147901/

Erin Lo (1):
  arm: dts: mt2701: Use real clock for UARTs

James Liao (1):
  arm: dts: mt2701: Add clock controller device nodes

Shunli Wang (2):
  clk: mediatek: Add MT2701 clock support
  reset: mediatek: Add MT2701 reset driver

 arch/arm/boot/dts/mt2701.dtsi          |   50 +-
 drivers/clk/mediatek/Kconfig           |   43 ++
 drivers/clk/mediatek/Makefile          |    7 +
 drivers/clk/mediatek/clk-gate.c        |   52 ++
 drivers/clk/mediatek/clk-gate.h        |    2 +
 drivers/clk/mediatek/clk-mt2701-bdp.c  |  148 +++++
 drivers/clk/mediatek/clk-mt2701-eth.c  |   90 +++
 drivers/clk/mediatek/clk-mt2701-hif.c  |   91 +++
 drivers/clk/mediatek/clk-mt2701-img.c  |   90 +++
 drivers/clk/mediatek/clk-mt2701-mm.c   |  133 ++++
 drivers/clk/mediatek/clk-mt2701-vdec.c |  101 +++
 drivers/clk/mediatek/clk-mt2701.c      | 1054 ++++++++++++++++++++++++++++++++
 drivers/clk/mediatek/clk-mtk.c         |   40 ++
 drivers/clk/mediatek/clk-mtk.h         |   41 +-
 drivers/clk/mediatek/clk-pll.c         |    1 +
 15 files changed, 1933 insertions(+), 10 deletions(-)
 create mode 100644 drivers/clk/mediatek/clk-mt2701-bdp.c
 create mode 100644 drivers/clk/mediatek/clk-mt2701-eth.c
 create mode 100644 drivers/clk/mediatek/clk-mt2701-hif.c
 create mode 100644 drivers/clk/mediatek/clk-mt2701-img.c
 create mode 100644 drivers/clk/mediatek/clk-mt2701-mm.c
 create mode 100644 drivers/clk/mediatek/clk-mt2701-vdec.c
 create mode 100644 drivers/clk/mediatek/clk-mt2701.c

--
1.9.1

^ permalink raw reply

* Re: [PATCH V5 3/4] drm/bridge: Add driver for GE B850v3 LVDS/DP++ Bridge
From: Peter Senna Tschudin @ 2016-09-26  8:58 UTC (permalink / raw)
  To: linux-arm-kernel
In-Reply-To: <c157d4a6-2d63-07d6-4274-883d79c8ab9e@codeaurora.org>

 Hi Archit,

On Monday, September 26, 2016 10:31 CEST, Archit Taneja <architt@codeaurora.org> wrote: 
 
> Hi Peter,
> 
> On 09/26/2016 01:57 PM, Peter Senna Tschudin wrote:
> > Patch 1/4 is already on linux-next, but what about this one? Ping?

> 
> I'd posted some queries a couple of times which you didn't answer to.
> Could you please respond to them before we try to get this merged?

Your queries were already answered by similar questions. The commit messages and cover letter also addresses the design decisions of the code. But basically the driver usefulness to other scenarios is severely limited by the firmware used by both chips. And when using the firmware that goes with this specific hardware, then yes, the two chips are always expected to work together. But the main point is that with the custom firmware each chip do not behave as independent bridges anymore.

On the other side, I was careful to use meaningful names for the registers, so a future implementation based on same chips can take the basics from this work, at least as a starting point.

Thanks,

Peter

^ permalink raw reply

* [PATCH 1/2] pwm: sunxi: allow the pwm to finish its pulse before disable
From: Olliver Schinagl @ 2016-09-26  8:46 UTC (permalink / raw)
  To: linux-arm-kernel
In-Reply-To: <20160924202502.GF16901@lukather>

On za, 2016-09-24 at 22:25 +0200, Maxime Ripard wrote:
> Hi Oliver,
> 
> Sorry for the slow answer.
> 
> On Fri, Sep 09, 2016 at 11:01:08AM +0200, Olliver Schinagl wrote:
> > 
> > > 
> > > > 
> > > > > 
> > > > > > 
> > > > > > *chip, struct pwm_device *pwm)
> > > > > > ?	spin_lock(&sun4i_pwm->ctrl_lock);
> > > > > > ?	val = sun4i_pwm_readl(sun4i_pwm, PWM_CTRL_REG);
> > > > > > ?	val &= ~BIT_CH(PWM_EN, pwm->hwpwm);
> > > > > > +	sun4i_pwm_writel(sun4i_pwm, val, PWM_CTRL_REG);
> > > > > > +	spin_unlock(&sun4i_pwm->ctrl_lock);
> > > > > > +
> > > > > > +	/* Allow for the PWM hardware to finish its last
> > > > > > toggle.
> > > > > > The pulse
> > > > > > +	?* may have just started and thus we should wait a
> > > > > > full
> > > > > > period.
> > > > > > +	?*/
> > > > > > +	ndelay(pwm_get_period(pwm));
> > > > > 
> > > > > Can't that use the ready bit as well?
> > > > It depends whatever is cheaper. If we disable the pwm, we have
> > > > to
> > > > commit that request to hardware first. Then we have to read
> > > > back
> > > > the
> > > > has ready and in the strange situation it is not, wait for it
> > > > to
> > > > become
> > > > ready?
> > > 
> > > If it works like you were suggesting, yes.
> > > 
> > > > 
> > > > 
> > > > Also, that would mean we would loop in a spin lock, or keep
> > > > setting/clearing an additional spinlock to read the ready bit.
> > > 
> > > You're using a spin_lock, so it's not that bad, but I was just
> > > suggesting replacing the ndelay.
> > 
> > If you say the spin_lock + wait for the ready is just as expensive
> > as
> > the ndelay, or the ndelay is less preferred, then I gladly make the
> > change;
> 
> For the spin_lock part, I was just comparing it to a
> spin_lock_irqsave, which is pretty expensive since it masks all the
> interrupts in the system, introducing latencies.
so spin_lock is very expensive and we should avoid if we can?
> 
> > 
> > but I think we need the ndelay for the else where we do not
> > have the ready flag (A10 or A13 iirc?)
> 
> Hmmmm, good point. But that would also apply to your second patch
> then, wouldn't it?
yeah, you would have an if/else for the case of !hasready.

this is what i've been dabbling in the train last week, but haven't
thought it through yet, let alone tested it:


+???????if (!(sun4i_pwm->data->has_rdy))
+???????????????ndelay(pwm_get_period(pwm));
+???????else
+???????????????do {
+???????????????????????spin_lock(&sun4i_pwm->ctrl_lock);
+???????????????????????val = sun4i_pwm_readl(sun4i_pwm, PWM_CTRL_REG);
+???????????????????????spin_unlock(&sun4i_pwm->ctrl_lock);
+???????????????} while (!(val & PWM_RDY(pwm->hwpwm)))

Here I assumed the spin_lock is cheap to make, expensive to hold for
long, e.g. reducing the length the spin-lock is active for. the
alternative was to remove the spin_lock here, and remove unlock-lock
before-after this block where you basically get a very long lasting
spin_lock, the alternative.
?
????????spin_lock(&sun4i_pwm->ctrl_lock);
????????val = sun4i_pwm_readl(sun4i_pwm, PWM_CTRL_REG);
+???????if (sun4i_pwm->data->has_rdy && (!(val & PWM_RDY(pwm->hwpwm))))
+???????????????dev_warn(chip->dev, "never became ready\n");

this may be useful for debugging i thought.

????????val &= ~BIT_CH(PWM_CLK_GATING, pwm->hwpwm);
????????sun4i_pwm_writel(sun4i_pwm, val, PWM_CTRL_REG);
????????spin_unlock(&sun4i_pwm->ctrl_lock);

Olliver
> 
> Maxime
> 
-------------- next part --------------
A non-text attachment was scrubbed...
Name: signature.asc
Type: application/pgp-signature
Size: 819 bytes
Desc: This is a digitally signed message part
URL: <http://lists.infradead.org/pipermail/linux-arm-kernel/attachments/20160926/8be18410/attachment-0001.sig>

^ permalink raw reply

* [PATCH v6 1/3] ASoC: sun4i-codec: Rename sun4i_codec_widgets for consistency
From: Maxime Ripard @ 2016-09-26  8:35 UTC (permalink / raw)
  To: linux-arm-kernel
In-Reply-To: <20160924200503.10728-2-dannym@scratchpost.org>

On Sat, Sep 24, 2016 at 10:05:01PM +0200, Danny Milosavljevic wrote:
> Rename "sun4i_codec_widgets" to "sun4i_codec_controls" for
> consistency with the struct field name.
> 
> Signed-off-by: Danny Milosavljevic <dannym@scratchpost.org>

Acked-by: Maxime Ripard <maxime.ripard@free-electrons.com>

Thanks!
Maxime

-- 
Maxime Ripard, Free Electrons
Embedded Linux and Kernel engineering
http://free-electrons.com
-------------- next part --------------
A non-text attachment was scrubbed...
Name: signature.asc
Type: application/pgp-signature
Size: 819 bytes
Desc: not available
URL: <http://lists.infradead.org/pipermail/linux-arm-kernel/attachments/20160926/1c61f098/attachment.sig>

^ permalink raw reply

* [PATCH V5 3/4] drm/bridge: Add driver for GE B850v3 LVDS/DP++ Bridge
From: Archit Taneja @ 2016-09-26  8:31 UTC (permalink / raw)
  To: linux-arm-kernel
In-Reply-To: <3423-57e8dc00-3-4c8bae80@193232935>

Hi Peter,

On 09/26/2016 01:57 PM, Peter Senna Tschudin wrote:
> Patch 1/4 is already on linux-next, but what about this one? Ping?

I'd posted some queries a couple of times which you didn't answer to.
Could you please respond to them before we try to get this merged?

Archit

>
> On Tuesday, August 9, 2016 18:41 CEST, Peter Senna Tschudin <peter.senna@collabora.com> wrote:
>
>> Add a driver that create a drm_bridge and a drm_connector for the LVDS
>> to DP++ display bridge of the GE B850v3.
>>
>> There are two physical bridges on the video signal pipeline: a
>> STDP4028(LVDS to DP) and a STDP2690(DP to DP++).  The hardware and
>> firmware made it complicated for this binding to comprise two device
>> tree nodes, as the design goal is to configure both bridges based on
>> the LVDS signal, which leave the driver powerless to control the video
>> processing pipeline. The two bridges behaves as a single bridge, and
>> the driver is only needed for telling the host about EDID / HPD, and
>> for giving the host powers to ack interrupts. The video signal pipeline
>> is as follows:
>>
>>   Host -> LVDS|--(STDP4028)--|DP -> DP|--(STDP2690)--|DP++ -> Video output
>>
>> Cc: Martyn Welch <martyn.welch@collabora.co.uk>
>> Cc: Martin Donnelly <martin.donnelly@ge.com>
>> Cc: Daniel Vetter <daniel.vetter@ffwll.ch>
>> Cc: Enric Balletbo i Serra <enric.balletbo@collabora.com>
>> Cc: Philipp Zabel <p.zabel@pengutronix.de>
>> Cc: Rob Herring <robh@kernel.org>
>> Cc: Fabio Estevam <fabio.estevam@nxp.com>
>> CC: David Airlie <airlied@linux.ie>
>> CC: Thierry Reding <treding@nvidia.com>
>> CC: Thierry Reding <thierry.reding@gmail.com>
>> Reviewed-by: Enric Balletbo <enric.balletbo@collabora.com>
>> Signed-off-by: Peter Senna Tschudin <peter.senna@collabora.com>
>> ---
>> Changes from V4:
>>  - Check the output of the first call to i2c_smbus_write_word_data() and return
>>    it's error code for failing gracefully on i2c issues
>>  - Renamed the i2c_driver.name from "ge,b850v3-lvds-dp" to "b850v3-lvds-dp" to
>>    remove the comma from the driver name
>>
>> Changes from V3:
>>  - 3/4 instead of 4/5
>>  - Tested on next-20160804
>>
>> Changes from V2:
>>  - Made it atomic to be applied on next-20160729 on top of Liu Ying changes
>>    that made imx-ldb atomic
>>
>> Changes from V1:
>>  - New commit message
>>  - Removed 3 empty entry points
>>  - Removed memory leak from ge_b850v3_lvds_dp_get_modes()
>>  - Added a lock for mode setting
>>  - Removed a few blank lines
>>  - Changed the order at Makefile and Kconfig
>>
>>  MAINTAINERS                                |   8 +
>>  drivers/gpu/drm/bridge/Kconfig             |  11 +
>>  drivers/gpu/drm/bridge/Makefile            |   1 +
>>  drivers/gpu/drm/bridge/ge_b850v3_lvds_dp.c | 405 +++++++++++++++++++++++++++++
>>  4 files changed, 425 insertions(+)
>>  create mode 100644 drivers/gpu/drm/bridge/ge_b850v3_lvds_dp.c
>>
>> diff --git a/MAINTAINERS b/MAINTAINERS
>> index a306795..e8d106a 100644
>> --- a/MAINTAINERS
>> +++ b/MAINTAINERS
>> @@ -5142,6 +5142,14 @@ W:	https://linuxtv.org
>>  S:	Maintained
>>  F:	drivers/media/radio/radio-gemtek*
>>
>> +GENERAL ELECTRIC B850V3 LVDS/DP++ BRIDGE
>> +M:	Peter Senna Tschudin <peter.senna@collabora.com>
>> +M:	Martin Donnelly <martin.donnelly@ge.com>
>> +M:	Martyn Welch <martyn.welch@collabora.co.uk>
>> +S:	Maintained
>> +F:	drivers/gpu/drm/bridge/ge_b850v3_dp2.c
>> +F:	Documentation/devicetree/bindings/ge/b850v3_dp2_bridge.txt
>> +
>>  GENERIC GPIO I2C DRIVER
>>  M:	Haavard Skinnemoen <hskinnemoen@gmail.com>
>>  S:	Supported
>> diff --git a/drivers/gpu/drm/bridge/Kconfig b/drivers/gpu/drm/bridge/Kconfig
>> index b590e67..b4b70fb 100644
>> --- a/drivers/gpu/drm/bridge/Kconfig
>> +++ b/drivers/gpu/drm/bridge/Kconfig
>> @@ -32,6 +32,17 @@ config DRM_DW_HDMI_AHB_AUDIO
>>  	  Designware HDMI block.  This is used in conjunction with
>>  	  the i.MX6 HDMI driver.
>>
>> +config DRM_GE_B850V3_LVDS_DP
>> +	tristate "GE B850v3 LVDS to DP++ display bridge"
>> +	depends on OF
>> +	select DRM_KMS_HELPER
>> +	select DRM_PANEL
>> +	---help---
>> +          This is a driver for the display bridge of
>> +          GE B850v3 that convert dual channel LVDS
>> +          to DP++. This is used with the i.MX6 imx-ldb
>> +          driver.
>> +
>>  config DRM_NXP_PTN3460
>>  	tristate "NXP PTN3460 DP/LVDS bridge"
>>  	depends on OF
>> diff --git a/drivers/gpu/drm/bridge/Makefile b/drivers/gpu/drm/bridge/Makefile
>> index efdb07e..b9606f3 100644
>> --- a/drivers/gpu/drm/bridge/Makefile
>> +++ b/drivers/gpu/drm/bridge/Makefile
>> @@ -3,6 +3,7 @@ ccflags-y := -Iinclude/drm
>>  obj-$(CONFIG_DRM_ANALOGIX_ANX78XX) += analogix-anx78xx.o
>>  obj-$(CONFIG_DRM_DW_HDMI) += dw-hdmi.o
>>  obj-$(CONFIG_DRM_DW_HDMI_AHB_AUDIO) += dw-hdmi-ahb-audio.o
>> +obj-$(CONFIG_DRM_GE_B850V3_LVDS_DP) += ge_b850v3_lvds_dp.o
>>  obj-$(CONFIG_DRM_NXP_PTN3460) += nxp-ptn3460.o
>>  obj-$(CONFIG_DRM_PARADE_PS8622) += parade-ps8622.o
>>  obj-$(CONFIG_DRM_SII902X) += sii902x.o
>> diff --git a/drivers/gpu/drm/bridge/ge_b850v3_lvds_dp.c b/drivers/gpu/drm/bridge/ge_b850v3_lvds_dp.c
>> new file mode 100644
>> index 0000000..81e9279
>> --- /dev/null
>> +++ b/drivers/gpu/drm/bridge/ge_b850v3_lvds_dp.c
>> @@ -0,0 +1,405 @@
>> +/*
>> + * Driver for GE B850v3 DP display bridge
>> +
>> + * Copyright (c) 2016, Collabora Ltd.
>> + * Copyright (c) 2016, General Electric Company
>> +
>> + * This program is free software; you can redistribute it and/or modify it
>> + * under the terms and conditions of the GNU General Public License,
>> + * version 2, as published by the Free Software Foundation.
>> +
>> + * This program is distributed in the hope it will be useful, but WITHOUT
>> + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
>> + * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License for
>> + * more details.
>> +
>> + * You should have received a copy of the GNU General Public License
>> + * along with this program.  If not, see <http://www.gnu.org/licenses/>.
>> +
>> + * This driver creates a drm_bridge and a drm_connector for the LVDS to DP++
>> + * display bridge of the GE B850v3. There are two physical bridges on the video
>> + * signal pipeline: a STDP4028(LVDS to DP) and a STDP2690(DP to DP++). However
>> + * the physical bridges are automatically configured by the input video signal,
>> + * and the driver has no access to the video processing pipeline. The driver is
>> + * only needed to read EDID from the STDP2690 and to handle HPD events from the
>> + * STDP4028. The driver communicates with both bridges over i2c. The video
>> + * signal pipeline is as follows:
>> + *
>> + *   Host -> LVDS|--(STDP4028)--|DP -> DP|--(STDP2690)--|DP++ -> Video output
>> + *
>> + */
>> +
>> +#include <linux/gpio.h>
>> +#include <linux/i2c.h>
>> +#include <linux/module.h>
>> +#include <linux/of.h>
>> +#include <drm/drm_atomic.h>
>> +#include <drm/drm_atomic_helper.h>
>> +#include <drm/drm_crtc_helper.h>
>> +#include <drm/drm_edid.h>
>> +#include <drm/drmP.h>
>> +
>> +/*
>> + * 220Mhz is a limitation of the host, as the bridge is capable of up to
>> + * 330Mhz. See section 9.2.1.2.4 of the i.MX 6Dual/6Quad Applications
>> + * Processor Reference Manual for more information about the 220Mhz limit.
>> + * The imx-ldb driver will warn about clocks over 170Mhz, but it seem to work
>> + * fine.
>> + */
>> +#define MAX_PIXEL_CLOCK 220000
>> +
>> +#define EDID_EXT_BLOCK_CNT 0x7E
>> +
>> +#define STDP4028_IRQ_OUT_CONF_REG 0x02
>> +#define STDP4028_DPTX_IRQ_EN_REG 0x3C
>> +#define STDP4028_DPTX_IRQ_STS_REG 0x3D
>> +#define STDP4028_DPTX_STS_REG 0x3E
>> +
>> +#define STDP4028_DPTX_DP_IRQ_EN 0x1000
>> +
>> +#define STDP4028_DPTX_HOTPLUG_IRQ_EN 0x0400
>> +#define STDP4028_DPTX_LINK_CH_IRQ_EN 0x2000
>> +#define STDP4028_DPTX_IRQ_CONFIG \
>> +		(STDP4028_DPTX_LINK_CH_IRQ_EN | STDP4028_DPTX_HOTPLUG_IRQ_EN)
>> +
>> +#define STDP4028_DPTX_HOTPLUG_STS 0x0200
>> +#define STDP4028_DPTX_LINK_STS 0x1000
>> +#define STDP4028_CON_STATE_CONNECTED \
>> +		(STDP4028_DPTX_HOTPLUG_STS | STDP4028_DPTX_LINK_STS)
>> +
>> +#define STDP4028_DPTX_HOTPLUG_CH_STS 0x0400
>> +#define STDP4028_DPTX_LINK_CH_STS 0x2000
>> +#define STDP4028_DPTX_IRQ_CLEAR \
>> +		(STDP4028_DPTX_LINK_CH_STS | STDP4028_DPTX_HOTPLUG_CH_STS)
>> +
>> +struct ge_b850v3_lvds_dp {
>> +	struct drm_connector connector;
>> +	struct drm_bridge bridge;
>> +	struct i2c_client *ge_b850v3_lvds_dp_i2c;
>> +	struct i2c_client *edid_i2c;
>> +	struct edid *edid;
>> +	struct mutex lock;
>> +};
>> +
>> +static inline struct ge_b850v3_lvds_dp *
>> +		bridge_to_ge_b850v3_lvds_dp(struct drm_bridge *bridge)
>> +{
>> +	return container_of(bridge, struct ge_b850v3_lvds_dp, bridge);
>> +}
>> +
>> +static inline struct ge_b850v3_lvds_dp *
>> +		connector_to_ge_b850v3_lvds_dp(struct drm_connector *connector)
>> +{
>> +	return container_of(connector, struct ge_b850v3_lvds_dp, connector);
>> +}
>> +
>> +u8 *stdp2690_get_edid(struct i2c_client *client)
>> +{
>> +	struct i2c_adapter *adapter = client->adapter;
>> +	unsigned char start = 0x00;
>> +	unsigned int total_size;
>> +	u8 *block = kmalloc(EDID_LENGTH, GFP_KERNEL);
>> +
>> +	struct i2c_msg msgs[] = {
>> +		{
>> +			.addr	= client->addr,
>> +			.flags	= 0,
>> +			.len	= 1,
>> +			.buf	= &start,
>> +		}, {
>> +			.addr	= client->addr,
>> +			.flags	= I2C_M_RD,
>> +			.len	= EDID_LENGTH,
>> +			.buf	= block,
>> +		}
>> +	};
>> +
>> +	if (!block)
>> +		return NULL;
>> +
>> +	if (i2c_transfer(adapter, msgs, 2) != 2) {
>> +		DRM_ERROR("Unable to read EDID.\n");
>> +		goto err;
>> +	}
>> +
>> +	if (!drm_edid_block_valid(block, 0, false, NULL)) {
>> +		DRM_ERROR("Invalid EDID block\n");
>> +		goto err;
>> +	}
>> +
>> +	total_size = (block[EDID_EXT_BLOCK_CNT] + 1) * EDID_LENGTH;
>> +	if (total_size > EDID_LENGTH) {
>> +		kfree(block);
>> +		block = kmalloc(total_size, GFP_KERNEL);
>> +		if (!block)
>> +			return NULL;
>> +
>> +		/* Yes, read the entire buffer, and do not skip the first
>> +		 * EDID_LENGTH bytes.
>> +		 */
>> +		start = 0x00;
>> +		msgs[1].len = total_size;
>> +		msgs[1].buf = block;
>> +
>> +		if (i2c_transfer(adapter, msgs, 2) != 2) {
>> +			DRM_ERROR("Unable to read EDID extension blocks.\n");
>> +			goto err;
>> +		}
>> +	}
>> +
>> +	return block;
>> +
>> +err:
>> +	kfree(block);
>> +	return NULL;
>> +}
>> +
>> +static int ge_b850v3_lvds_dp_get_modes(struct drm_connector *connector)
>> +{
>> +	struct ge_b850v3_lvds_dp *ptn_bridge;
>> +	struct i2c_client *client;
>> +	int num_modes = 0;
>> +
>> +	ptn_bridge = connector_to_ge_b850v3_lvds_dp(connector);
>> +	client = ptn_bridge->edid_i2c;
>> +
>> +	mutex_lock(&ptn_bridge->lock);
>> +
>> +	kfree(ptn_bridge->edid);
>> +	ptn_bridge->edid = (struct edid *) stdp2690_get_edid(client);
>> +
>> +	if (ptn_bridge->edid) {
>> +		drm_mode_connector_update_edid_property(connector,
>> +				ptn_bridge->edid);
>> +		num_modes = drm_add_edid_modes(connector, ptn_bridge->edid);
>> +	}
>> +
>> +	mutex_unlock(&ptn_bridge->lock);
>> +
>> +	return num_modes;
>> +}
>> +
>> +
>> +static enum drm_mode_status ge_b850v3_lvds_dp_mode_valid(
>> +		struct drm_connector *connector, struct drm_display_mode *mode)
>> +{
>> +	if (mode->clock > MAX_PIXEL_CLOCK) {
>> +		DRM_INFO("The pixel clock for the mode %s is too high, and not supported.",
>> +				mode->name);
>> +		return MODE_CLOCK_HIGH;
>> +	}
>> +
>> +	return MODE_OK;
>> +}
>> +
>> +static const struct
>> +drm_connector_helper_funcs ge_b850v3_lvds_dp_connector_helper_funcs = {
>> +	.get_modes = ge_b850v3_lvds_dp_get_modes,
>> +	.mode_valid = ge_b850v3_lvds_dp_mode_valid,
>> +};
>> +
>> +static enum drm_connector_status ge_b850v3_lvds_dp_detect(
>> +		struct drm_connector *connector, bool force)
>> +{
>> +	struct ge_b850v3_lvds_dp *ptn_bridge =
>> +			connector_to_ge_b850v3_lvds_dp(connector);
>> +	struct i2c_client *ge_b850v3_lvds_dp_i2c =
>> +			ptn_bridge->ge_b850v3_lvds_dp_i2c;
>> +	s32 link_state;
>> +
>> +	link_state = i2c_smbus_read_word_data(ge_b850v3_lvds_dp_i2c,
>> +			STDP4028_DPTX_STS_REG);
>> +
>> +	if (link_state == STDP4028_CON_STATE_CONNECTED)
>> +		return connector_status_connected;
>> +
>> +	if (link_state == 0)
>> +		return connector_status_disconnected;
>> +
>> +	return connector_status_unknown;
>> +}
>> +
>> +static const struct drm_connector_funcs ge_b850v3_lvds_dp_connector_funcs = {
>> +	.dpms = drm_atomic_helper_connector_dpms,
>> +	.fill_modes = drm_helper_probe_single_connector_modes,
>> +	.detect = ge_b850v3_lvds_dp_detect,
>> +	.destroy = drm_connector_cleanup,
>> +	.reset = drm_atomic_helper_connector_reset,
>> +	.atomic_duplicate_state = drm_atomic_helper_connector_duplicate_state,
>> +	.atomic_destroy_state = drm_atomic_helper_connector_destroy_state,
>> +};
>> +
>> +static irqreturn_t ge_b850v3_lvds_dp_irq_handler(int irq, void *dev_id)
>> +{
>> +	struct ge_b850v3_lvds_dp *ptn_bridge = dev_id;
>> +	struct i2c_client *ge_b850v3_lvds_dp_i2c
>> +			= ptn_bridge->ge_b850v3_lvds_dp_i2c;
>> +
>> +	mutex_lock(&ptn_bridge->lock);
>> +
>> +	i2c_smbus_write_word_data(ge_b850v3_lvds_dp_i2c,
>> +			STDP4028_DPTX_IRQ_STS_REG, STDP4028_DPTX_IRQ_CLEAR);
>> +
>> +	mutex_unlock(&ptn_bridge->lock);
>> +
>> +	if (ptn_bridge->connector.dev)
>> +		drm_kms_helper_hotplug_event(ptn_bridge->connector.dev);
>> +
>> +	return IRQ_HANDLED;
>> +}
>> +
>> +static int ge_b850v3_lvds_dp_attach(struct drm_bridge *bridge)
>> +{
>> +	struct ge_b850v3_lvds_dp *ptn_bridge
>> +			= bridge_to_ge_b850v3_lvds_dp(bridge);
>> +	struct drm_connector *connector = &ptn_bridge->connector;
>> +	struct i2c_client *ge_b850v3_lvds_dp_i2c
>> +			= ptn_bridge->ge_b850v3_lvds_dp_i2c;
>> +	int ret;
>> +
>> +	if (!bridge->encoder) {
>> +		DRM_ERROR("Parent encoder object not found");
>> +		return -ENODEV;
>> +	}
>> +
>> +	connector->polled = DRM_CONNECTOR_POLL_HPD;
>> +
>> +	drm_connector_helper_add(connector,
>> +			&ge_b850v3_lvds_dp_connector_helper_funcs);
>> +
>> +	ret = drm_connector_init(bridge->dev, connector,
>> +			&ge_b850v3_lvds_dp_connector_funcs,
>> +			DRM_MODE_CONNECTOR_DisplayPort);
>> +	if (ret) {
>> +		DRM_ERROR("Failed to initialize connector with drm\n");
>> +		return ret;
>> +	}
>> +
>> +	drm_connector_register(connector);
>> +	ret = drm_mode_connector_attach_encoder(connector, bridge->encoder);
>> +	if (ret)
>> +		return ret;
>> +
>> +	drm_bridge_enable(bridge);
>> +	if (ge_b850v3_lvds_dp_i2c->irq) {
>> +		drm_helper_hpd_irq_event(connector->dev);
>> +
>> +		ret = devm_request_threaded_irq(&ge_b850v3_lvds_dp_i2c->dev,
>> +				ge_b850v3_lvds_dp_i2c->irq, NULL,
>> +				ge_b850v3_lvds_dp_irq_handler,
>> +				IRQF_TRIGGER_HIGH | IRQF_ONESHOT,
>> +				"ge-b850v3-lvds-dp", ptn_bridge);
>> +		if (ret)
>> +			return ret;
>> +	}
>> +
>> +	return 0;
>> +}
>> +
>> +static const struct drm_bridge_funcs ge_b850v3_lvds_dp_funcs = {
>> +	.attach = ge_b850v3_lvds_dp_attach,
>> +};
>> +
>> +static int ge_b850v3_lvds_dp_probe(struct i2c_client *ge_b850v3_lvds_dp_i2c,
>> +				const struct i2c_device_id *id)
>> +{
>> +	struct device *dev = &ge_b850v3_lvds_dp_i2c->dev;
>> +	struct ge_b850v3_lvds_dp *ptn_bridge;
>> +	int ret;
>> +	u32 edid_i2c_reg;
>> +
>> +	ptn_bridge = devm_kzalloc(dev, sizeof(*ptn_bridge), GFP_KERNEL);
>> +	if (!ptn_bridge)
>> +		return -ENOMEM;
>> +
>> +	mutex_init(&ptn_bridge->lock);
>> +
>> +	ptn_bridge->ge_b850v3_lvds_dp_i2c = ge_b850v3_lvds_dp_i2c;
>> +	ptn_bridge->bridge.driver_private = ptn_bridge;
>> +	i2c_set_clientdata(ge_b850v3_lvds_dp_i2c, ptn_bridge);
>> +
>> +	ret = of_property_read_u32(dev->of_node, "edid-reg", &edid_i2c_reg);
>> +	if (ret) {
>> +		dev_err(dev, "edid-reg not specified, aborting...\n");
>> +		return -ENODEV;
>> +	}
>> +
>> +	ptn_bridge->edid_i2c = devm_kzalloc(dev,
>> +			sizeof(struct i2c_client), GFP_KERNEL);
>> +
>> +	if (!ptn_bridge->edid_i2c)
>> +		return -ENOMEM;
>> +
>> +	memcpy(ptn_bridge->edid_i2c, ge_b850v3_lvds_dp_i2c,
>> +			sizeof(struct i2c_client));
>> +
>> +	ptn_bridge->edid_i2c->addr = (unsigned short) edid_i2c_reg;
>> +
>> +	/*
>> +	 * Configures the bridge to re-enable interrupts after each ack. As
>> +	 * this is the first communication with the chip, fail on error.
>> +	 */
>> +	ret = i2c_smbus_write_word_data(ge_b850v3_lvds_dp_i2c,
>> +			STDP4028_IRQ_OUT_CONF_REG, STDP4028_DPTX_DP_IRQ_EN);
>> +	if (ret) {
>> +		dev_err(dev, "i2c communication failed, aborting...\n");
>> +		return ret;
>> +	}
>> +
>> +	i2c_smbus_write_word_data(ge_b850v3_lvds_dp_i2c,
>> +			STDP4028_DPTX_IRQ_EN_REG, STDP4028_DPTX_IRQ_CONFIG);
>> +
>> +	/* Clear pending interrupts since power up. */
>> +	i2c_smbus_write_word_data(ge_b850v3_lvds_dp_i2c,
>> +			STDP4028_DPTX_IRQ_STS_REG, STDP4028_DPTX_IRQ_CLEAR);
>> +
>> +	ptn_bridge->bridge.funcs = &ge_b850v3_lvds_dp_funcs;
>> +	ptn_bridge->bridge.of_node = dev->of_node;
>> +	ret = drm_bridge_add(&ptn_bridge->bridge);
>> +	if (ret) {
>> +		DRM_ERROR("Failed to add bridge\n");
>> +		return ret;
>> +	}
>> +
>> +	return 0;
>> +}
>> +
>> +static int ge_b850v3_lvds_dp_remove(struct i2c_client *ge_b850v3_lvds_dp_i2c)
>> +{
>> +	struct ge_b850v3_lvds_dp *ptn_bridge =
>> +		i2c_get_clientdata(ge_b850v3_lvds_dp_i2c);
>> +
>> +	drm_bridge_remove(&ptn_bridge->bridge);
>> +
>> +	kfree(ptn_bridge->edid);
>> +
>> +	return 0;
>> +}
>> +
>> +static const struct i2c_device_id ge_b850v3_lvds_dp_i2c_table[] = {
>> +	{"b850v3-lvds-dp", 0},
>> +	{},
>> +};
>> +MODULE_DEVICE_TABLE(i2c, ge_b850v3_lvds_dp_i2c_table);
>> +
>> +static const struct of_device_id ge_b850v3_lvds_dp_match[] = {
>> +	{ .compatible = "ge,b850v3-lvds-dp" },
>> +	{},
>> +};
>> +MODULE_DEVICE_TABLE(of, ge_b850v3_lvds_dp_match);
>> +
>> +static struct i2c_driver ge_b850v3_lvds_dp_driver = {
>> +	.id_table	= ge_b850v3_lvds_dp_i2c_table,
>> +	.probe		= ge_b850v3_lvds_dp_probe,
>> +	.remove		= ge_b850v3_lvds_dp_remove,
>> +	.driver		= {
>> +		.name		= "b850v3-lvds-dp",
>> +		.of_match_table = ge_b850v3_lvds_dp_match,
>> +	},
>> +};
>> +module_i2c_driver(ge_b850v3_lvds_dp_driver);
>> +
>> +MODULE_AUTHOR("Peter Senna Tschudin <peter.senna@collabora.com>");
>> +MODULE_AUTHOR("Martyn Welch <martyn.welch@collabora.co.uk>");
>> +MODULE_DESCRIPTION("GE LVDS to DP++ display bridge)");
>> +MODULE_LICENSE("GPL v2");
>> --
>> 2.5.5
>>
>
>
>
>
>
>

-- 
Qualcomm Innovation Center, Inc. is a member of Code Aurora Forum,
a Linux Foundation Collaborative Project

^ permalink raw reply

* Re: [PATCH V5 4/4] dts/imx6q-b850v3: Use GE B850v3 LVDS/DP++ Bridge
From: Peter Senna Tschudin @ 2016-09-26  8:27 UTC (permalink / raw)
  To: linux-arm-kernel
In-Reply-To: <61b63c6a5fec9fce0332e5e9db676e6b6a6fc180.1470739193.git.peter.senna@collabora.com>

Patch 1/4 is already on linux-next, but what about this one? Ping?

On Tuesday, August 9, 2016 18:41 CEST, Peter Senna Tschudin <peter.senna@collabora.com> wrote: 
 
> Configures the GE B850v3 LVDS/DP++ bridge on the dts file.
> 
> Cc: Martyn Welch <martyn.welch@collabora.co.uk>
> Cc: Martin Donnelly <martin.donnelly@ge.com>
> Cc: Javier Martinez Canillas <javier@dowhile0.org>
> Cc: Enric Balletbo i Serra <enric.balletbo@collabora.com>
> Cc: Philipp Zabel <p.zabel@pengutronix.de>
> Cc: Rob Herring <robh@kernel.org>
> Cc: Fabio Estevam <fabio.estevam@nxp.com>
> Signed-off-by: Peter Senna Tschudin <peter.senna@collabora.com>
> ---
> Unchanged from V4
> 
> Changes from V3:
>  - 4/4 instead of 5/5
> 
> Unchanged from V2
> 
> Changes from V1:
>  - Replaced '_' by '-' in node names or compatible strings
>  - Added missing @73 to b850v3-lvds-dp-bridge
> 
>  arch/arm/boot/dts/imx6q-b850v3.dts | 30 ++++++++++++++++++++++++++++++
>  1 file changed, 30 insertions(+)
> 
> diff --git a/arch/arm/boot/dts/imx6q-b850v3.dts b/arch/arm/boot/dts/imx6q-b850v3.dts
> index 167f744..8db3bf2 100644
> --- a/arch/arm/boot/dts/imx6q-b850v3.dts
> +++ b/arch/arm/boot/dts/imx6q-b850v3.dts
> @@ -72,6 +72,13 @@
>  		fsl,data-mapping = "spwg";
>  		fsl,data-width = <24>;
>  		status = "okay";
> +
> +		port at 4 {
> +			reg = <4>;
> +			lvds0_out: endpoint {
> +				remote-endpoint = <&b850v3_lvds_dp_bridge_in>;
> +			};
> +		};
>  	};
>  };
>  
> @@ -142,3 +149,26 @@
>  		reg = <0x4a>;
>  	};
>  };
> +
> +&mux2_i2c2 {
> +	status = "okay";
> +	clock-frequency = <100000>;
> +
> +	b850v3-lvds-dp-bridge at 73 {
> +		compatible = "ge,b850v3-lvds-dp";
> +		#address-cells = <1>;
> +		#size-cells = <0>;
> +
> +		reg = <0x73>;
> +		interrupt-parent = <&gpio2>;
> +		interrupts = <0 IRQ_TYPE_LEVEL_HIGH>;
> +
> +		edid-reg = <0x72>;
> +
> +		port {
> +			b850v3_lvds_dp_bridge_in: endpoint {
> +				remote-endpoint = <&lvds0_out>;
> +			};
> +		};
> +	};
> +};
> -- 
> 2.5.5
> 
 
 
 
 

^ permalink raw reply

* Re: [PATCH V5 3/4] drm/bridge: Add driver for GE B850v3 LVDS/DP++ Bridge
From: Peter Senna Tschudin @ 2016-09-26  8:27 UTC (permalink / raw)
  To: linux-arm-kernel
In-Reply-To: <37d177d89841be8ff8eec395e3267d1ebf0dfc54.1470739193.git.peter.senna@collabora.com>

Patch 1/4 is already on linux-next, but what about this one? Ping?

On Tuesday, August 9, 2016 18:41 CEST, Peter Senna Tschudin <peter.senna@collabora.com> wrote: 
 
> Add a driver that create a drm_bridge and a drm_connector for the LVDS
> to DP++ display bridge of the GE B850v3.
> 
> There are two physical bridges on the video signal pipeline: a
> STDP4028(LVDS to DP) and a STDP2690(DP to DP++).  The hardware and
> firmware made it complicated for this binding to comprise two device

> tree nodes, as the design goal is to configure both bridges based on

> the LVDS signal, which leave the driver powerless to control the video
> processing pipeline. The two bridges behaves as a single bridge, and

> the driver is only needed for telling the host about EDID / HPD, and

> for giving the host powers to ack interrupts. The video signal pipeline
> is as follows:
> 
>   Host -> LVDS|--(STDP4028)--|DP -> DP|--(STDP2690)--|DP++ -> Video output
> 
> Cc: Martyn Welch <martyn.welch@collabora.co.uk>
> Cc: Martin Donnelly <martin.donnelly@ge.com>
> Cc: Daniel Vetter <daniel.vetter@ffwll.ch>
> Cc: Enric Balletbo i Serra <enric.balletbo@collabora.com>
> Cc: Philipp Zabel <p.zabel@pengutronix.de>
> Cc: Rob Herring <robh@kernel.org>
> Cc: Fabio Estevam <fabio.estevam@nxp.com>
> CC: David Airlie <airlied@linux.ie>
> CC: Thierry Reding <treding@nvidia.com>
> CC: Thierry Reding <thierry.reding@gmail.com>
> Reviewed-by: Enric Balletbo <enric.balletbo@collabora.com>
> Signed-off-by: Peter Senna Tschudin <peter.senna@collabora.com>
> ---
> Changes from V4:
>  - Check the output of the first call to i2c_smbus_write_word_data() and return
>    it's error code for failing gracefully on i2c issues
>  - Renamed the i2c_driver.name from "ge,b850v3-lvds-dp" to "b850v3-lvds-dp" to
>    remove the comma from the driver name
> 
> Changes from V3:
>  - 3/4 instead of 4/5
>  - Tested on next-20160804
> 
> Changes from V2:
>  - Made it atomic to be applied on next-20160729 on top of Liu Ying changes
>    that made imx-ldb atomic
> 
> Changes from V1:
>  - New commit message
>  - Removed 3 empty entry points
>  - Removed memory leak from ge_b850v3_lvds_dp_get_modes()
>  - Added a lock for mode setting
>  - Removed a few blank lines
>  - Changed the order at Makefile and Kconfig
> 
>  MAINTAINERS                                |   8 +
>  drivers/gpu/drm/bridge/Kconfig             |  11 +
>  drivers/gpu/drm/bridge/Makefile            |   1 +
>  drivers/gpu/drm/bridge/ge_b850v3_lvds_dp.c | 405 +++++++++++++++++++++++++++++
>  4 files changed, 425 insertions(+)
>  create mode 100644 drivers/gpu/drm/bridge/ge_b850v3_lvds_dp.c

> 
> diff --git a/MAINTAINERS b/MAINTAINERS
> index a306795..e8d106a 100644
> --- a/MAINTAINERS
> +++ b/MAINTAINERS
> @@ -5142,6 +5142,14 @@ W:	https://linuxtv.org
>  S:	Maintained
>  F:	drivers/media/radio/radio-gemtek*
>  
> +GENERAL ELECTRIC B850V3 LVDS/DP++ BRIDGE
> +M:	Peter Senna Tschudin <peter.senna@collabora.com>
> +M:	Martin Donnelly <martin.donnelly@ge.com>
> +M:	Martyn Welch <martyn.welch@collabora.co.uk>
> +S:	Maintained
> +F:	drivers/gpu/drm/bridge/ge_b850v3_dp2.c
> +F:	Documentation/devicetree/bindings/ge/b850v3_dp2_bridge.txt
> +
>  GENERIC GPIO I2C DRIVER
>  M:	Haavard Skinnemoen <hskinnemoen@gmail.com>
>  S:	Supported
> diff --git a/drivers/gpu/drm/bridge/Kconfig b/drivers/gpu/drm/bridge/Kconfig
> index b590e67..b4b70fb 100644
> --- a/drivers/gpu/drm/bridge/Kconfig
> +++ b/drivers/gpu/drm/bridge/Kconfig
> @@ -32,6 +32,17 @@ config DRM_DW_HDMI_AHB_AUDIO
>  	  Designware HDMI block.  This is used in conjunction with
>  	  the i.MX6 HDMI driver.
>  
> +config DRM_GE_B850V3_LVDS_DP
> +	tristate "GE B850v3 LVDS to DP++ display bridge"
> +	depends on OF
> +	select DRM_KMS_HELPER
> +	select DRM_PANEL
> +	---help---
> +          This is a driver for the display bridge of
> +          GE B850v3 that convert dual channel LVDS
> +          to DP++. This is used with the i.MX6 imx-ldb
> +          driver.
> +
>  config DRM_NXP_PTN3460
>  	tristate "NXP PTN3460 DP/LVDS bridge"
>  	depends on OF
> diff --git a/drivers/gpu/drm/bridge/Makefile b/drivers/gpu/drm/bridge/Makefile
> index efdb07e..b9606f3 100644
> --- a/drivers/gpu/drm/bridge/Makefile
> +++ b/drivers/gpu/drm/bridge/Makefile
> @@ -3,6 +3,7 @@ ccflags-y := -Iinclude/drm
>  obj-$(CONFIG_DRM_ANALOGIX_ANX78XX) += analogix-anx78xx.o
>  obj-$(CONFIG_DRM_DW_HDMI) += dw-hdmi.o
>  obj-$(CONFIG_DRM_DW_HDMI_AHB_AUDIO) += dw-hdmi-ahb-audio.o
> +obj-$(CONFIG_DRM_GE_B850V3_LVDS_DP) += ge_b850v3_lvds_dp.o
>  obj-$(CONFIG_DRM_NXP_PTN3460) += nxp-ptn3460.o
>  obj-$(CONFIG_DRM_PARADE_PS8622) += parade-ps8622.o
>  obj-$(CONFIG_DRM_SII902X) += sii902x.o
> diff --git a/drivers/gpu/drm/bridge/ge_b850v3_lvds_dp.c b/drivers/gpu/drm/bridge/ge_b850v3_lvds_dp.c
> new file mode 100644
> index 0000000..81e9279
> --- /dev/null
> +++ b/drivers/gpu/drm/bridge/ge_b850v3_lvds_dp.c
> @@ -0,0 +1,405 @@
> +/*
> + * Driver for GE B850v3 DP display bridge
> +
> + * Copyright (c) 2016, Collabora Ltd.
> + * Copyright (c) 2016, General Electric Company
> +
> + * This program is free software; you can redistribute it and/or modify it
> + * under the terms and conditions of the GNU General Public License,
> + * version 2, as published by the Free Software Foundation.
> +
> + * This program is distributed in the hope it will be useful, but WITHOUT
> + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
> + * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License for
> + * more details.
> +
> + * You should have received a copy of the GNU General Public License
> + * along with this program.  If not, see <http://www.gnu.org/licenses/>.
> +
> + * This driver creates a drm_bridge and a drm_connector for the LVDS to DP++
> + * display bridge of the GE B850v3. There are two physical bridges on the video
> + * signal pipeline: a STDP4028(LVDS to DP) and a STDP2690(DP to DP++). However
> + * the physical bridges are automatically configured by the input video signal,
> + * and the driver has no access to the video processing pipeline. The driver is
> + * only needed to read EDID from the STDP2690 and to handle HPD events from the
> + * STDP4028. The driver communicates with both bridges over i2c. The video
> + * signal pipeline is as follows:
> + *
> + *   Host -> LVDS|--(STDP4028)--|DP -> DP|--(STDP2690)--|DP++ -> Video output
> + *
> + */
> +
> +#include <linux/gpio.h>
> +#include <linux/i2c.h>
> +#include <linux/module.h>
> +#include <linux/of.h>
> +#include <drm/drm_atomic.h>
> +#include <drm/drm_atomic_helper.h>
> +#include <drm/drm_crtc_helper.h>
> +#include <drm/drm_edid.h>
> +#include <drm/drmP.h>
> +
> +/*
> + * 220Mhz is a limitation of the host, as the bridge is capable of up to
> + * 330Mhz. See section 9.2.1.2.4 of the i.MX 6Dual/6Quad Applications
> + * Processor Reference Manual for more information about the 220Mhz limit.
> + * The imx-ldb driver will warn about clocks over 170Mhz, but it seem to work
> + * fine.
> + */
> +#define MAX_PIXEL_CLOCK 220000
> +
> +#define EDID_EXT_BLOCK_CNT 0x7E
> +
> +#define STDP4028_IRQ_OUT_CONF_REG 0x02
> +#define STDP4028_DPTX_IRQ_EN_REG 0x3C
> +#define STDP4028_DPTX_IRQ_STS_REG 0x3D
> +#define STDP4028_DPTX_STS_REG 0x3E
> +
> +#define STDP4028_DPTX_DP_IRQ_EN 0x1000
> +
> +#define STDP4028_DPTX_HOTPLUG_IRQ_EN 0x0400
> +#define STDP4028_DPTX_LINK_CH_IRQ_EN 0x2000
> +#define STDP4028_DPTX_IRQ_CONFIG \
> +		(STDP4028_DPTX_LINK_CH_IRQ_EN | STDP4028_DPTX_HOTPLUG_IRQ_EN)
> +
> +#define STDP4028_DPTX_HOTPLUG_STS 0x0200
> +#define STDP4028_DPTX_LINK_STS 0x1000
> +#define STDP4028_CON_STATE_CONNECTED \
> +		(STDP4028_DPTX_HOTPLUG_STS | STDP4028_DPTX_LINK_STS)
> +
> +#define STDP4028_DPTX_HOTPLUG_CH_STS 0x0400
> +#define STDP4028_DPTX_LINK_CH_STS 0x2000
> +#define STDP4028_DPTX_IRQ_CLEAR \
> +		(STDP4028_DPTX_LINK_CH_STS | STDP4028_DPTX_HOTPLUG_CH_STS)
> +
> +struct ge_b850v3_lvds_dp {
> +	struct drm_connector connector;
> +	struct drm_bridge bridge;
> +	struct i2c_client *ge_b850v3_lvds_dp_i2c;
> +	struct i2c_client *edid_i2c;
> +	struct edid *edid;
> +	struct mutex lock;
> +};
> +
> +static inline struct ge_b850v3_lvds_dp *
> +		bridge_to_ge_b850v3_lvds_dp(struct drm_bridge *bridge)
> +{
> +	return container_of(bridge, struct ge_b850v3_lvds_dp, bridge);
> +}
> +
> +static inline struct ge_b850v3_lvds_dp *
> +		connector_to_ge_b850v3_lvds_dp(struct drm_connector *connector)
> +{
> +	return container_of(connector, struct ge_b850v3_lvds_dp, connector);
> +}
> +
> +u8 *stdp2690_get_edid(struct i2c_client *client)
> +{
> +	struct i2c_adapter *adapter = client->adapter;
> +	unsigned char start = 0x00;
> +	unsigned int total_size;
> +	u8 *block = kmalloc(EDID_LENGTH, GFP_KERNEL);
> +
> +	struct i2c_msg msgs[] = {
> +		{
> +			.addr	= client->addr,
> +			.flags	= 0,
> +			.len	= 1,
> +			.buf	= &start,
> +		}, {
> +			.addr	= client->addr,
> +			.flags	= I2C_M_RD,
> +			.len	= EDID_LENGTH,
> +			.buf	= block,
> +		}
> +	};
> +
> +	if (!block)
> +		return NULL;
> +
> +	if (i2c_transfer(adapter, msgs, 2) != 2) {
> +		DRM_ERROR("Unable to read EDID.\n");
> +		goto err;
> +	}
> +
> +	if (!drm_edid_block_valid(block, 0, false, NULL)) {
> +		DRM_ERROR("Invalid EDID block\n");
> +		goto err;
> +	}
> +
> +	total_size = (block[EDID_EXT_BLOCK_CNT] + 1) * EDID_LENGTH;
> +	if (total_size > EDID_LENGTH) {
> +		kfree(block);
> +		block = kmalloc(total_size, GFP_KERNEL);
> +		if (!block)
> +			return NULL;
> +
> +		/* Yes, read the entire buffer, and do not skip the first
> +		 * EDID_LENGTH bytes.
> +		 */
> +		start = 0x00;
> +		msgs[1].len = total_size;
> +		msgs[1].buf = block;
> +
> +		if (i2c_transfer(adapter, msgs, 2) != 2) {
> +			DRM_ERROR("Unable to read EDID extension blocks.\n");
> +			goto err;
> +		}
> +	}
> +
> +	return block;
> +
> +err:
> +	kfree(block);
> +	return NULL;
> +}
> +
> +static int ge_b850v3_lvds_dp_get_modes(struct drm_connector *connector)
> +{
> +	struct ge_b850v3_lvds_dp *ptn_bridge;
> +	struct i2c_client *client;
> +	int num_modes = 0;
> +
> +	ptn_bridge = connector_to_ge_b850v3_lvds_dp(connector);
> +	client = ptn_bridge->edid_i2c;
> +
> +	mutex_lock(&ptn_bridge->lock);
> +
> +	kfree(ptn_bridge->edid);
> +	ptn_bridge->edid = (struct edid *) stdp2690_get_edid(client);
> +
> +	if (ptn_bridge->edid) {
> +		drm_mode_connector_update_edid_property(connector,
> +				ptn_bridge->edid);
> +		num_modes = drm_add_edid_modes(connector, ptn_bridge->edid);
> +	}
> +
> +	mutex_unlock(&ptn_bridge->lock);
> +
> +	return num_modes;
> +}
> +
> +
> +static enum drm_mode_status ge_b850v3_lvds_dp_mode_valid(
> +		struct drm_connector *connector, struct drm_display_mode *mode)
> +{
> +	if (mode->clock > MAX_PIXEL_CLOCK) {
> +		DRM_INFO("The pixel clock for the mode %s is too high, and not supported.",
> +				mode->name);
> +		return MODE_CLOCK_HIGH;
> +	}
> +
> +	return MODE_OK;
> +}
> +
> +static const struct
> +drm_connector_helper_funcs ge_b850v3_lvds_dp_connector_helper_funcs = {
> +	.get_modes = ge_b850v3_lvds_dp_get_modes,
> +	.mode_valid = ge_b850v3_lvds_dp_mode_valid,
> +};
> +
> +static enum drm_connector_status ge_b850v3_lvds_dp_detect(
> +		struct drm_connector *connector, bool force)
> +{
> +	struct ge_b850v3_lvds_dp *ptn_bridge =
> +			connector_to_ge_b850v3_lvds_dp(connector);
> +	struct i2c_client *ge_b850v3_lvds_dp_i2c =
> +			ptn_bridge->ge_b850v3_lvds_dp_i2c;
> +	s32 link_state;
> +
> +	link_state = i2c_smbus_read_word_data(ge_b850v3_lvds_dp_i2c,
> +			STDP4028_DPTX_STS_REG);
> +
> +	if (link_state == STDP4028_CON_STATE_CONNECTED)
> +		return connector_status_connected;
> +
> +	if (link_state == 0)
> +		return connector_status_disconnected;
> +
> +	return connector_status_unknown;
> +}
> +
> +static const struct drm_connector_funcs ge_b850v3_lvds_dp_connector_funcs = {
> +	.dpms = drm_atomic_helper_connector_dpms,
> +	.fill_modes = drm_helper_probe_single_connector_modes,
> +	.detect = ge_b850v3_lvds_dp_detect,
> +	.destroy = drm_connector_cleanup,
> +	.reset = drm_atomic_helper_connector_reset,
> +	.atomic_duplicate_state = drm_atomic_helper_connector_duplicate_state,
> +	.atomic_destroy_state = drm_atomic_helper_connector_destroy_state,
> +};
> +
> +static irqreturn_t ge_b850v3_lvds_dp_irq_handler(int irq, void *dev_id)
> +{
> +	struct ge_b850v3_lvds_dp *ptn_bridge = dev_id;
> +	struct i2c_client *ge_b850v3_lvds_dp_i2c
> +			= ptn_bridge->ge_b850v3_lvds_dp_i2c;
> +
> +	mutex_lock(&ptn_bridge->lock);
> +
> +	i2c_smbus_write_word_data(ge_b850v3_lvds_dp_i2c,
> +			STDP4028_DPTX_IRQ_STS_REG, STDP4028_DPTX_IRQ_CLEAR);
> +
> +	mutex_unlock(&ptn_bridge->lock);
> +
> +	if (ptn_bridge->connector.dev)
> +		drm_kms_helper_hotplug_event(ptn_bridge->connector.dev);
> +
> +	return IRQ_HANDLED;
> +}
> +
> +static int ge_b850v3_lvds_dp_attach(struct drm_bridge *bridge)
> +{
> +	struct ge_b850v3_lvds_dp *ptn_bridge
> +			= bridge_to_ge_b850v3_lvds_dp(bridge);
> +	struct drm_connector *connector = &ptn_bridge->connector;
> +	struct i2c_client *ge_b850v3_lvds_dp_i2c
> +			= ptn_bridge->ge_b850v3_lvds_dp_i2c;
> +	int ret;
> +
> +	if (!bridge->encoder) {
> +		DRM_ERROR("Parent encoder object not found");
> +		return -ENODEV;
> +	}
> +
> +	connector->polled = DRM_CONNECTOR_POLL_HPD;
> +
> +	drm_connector_helper_add(connector,
> +			&ge_b850v3_lvds_dp_connector_helper_funcs);
> +
> +	ret = drm_connector_init(bridge->dev, connector,
> +			&ge_b850v3_lvds_dp_connector_funcs,
> +			DRM_MODE_CONNECTOR_DisplayPort);
> +	if (ret) {
> +		DRM_ERROR("Failed to initialize connector with drm\n");
> +		return ret;
> +	}
> +
> +	drm_connector_register(connector);
> +	ret = drm_mode_connector_attach_encoder(connector, bridge->encoder);
> +	if (ret)
> +		return ret;
> +
> +	drm_bridge_enable(bridge);
> +	if (ge_b850v3_lvds_dp_i2c->irq) {
> +		drm_helper_hpd_irq_event(connector->dev);
> +
> +		ret = devm_request_threaded_irq(&ge_b850v3_lvds_dp_i2c->dev,
> +				ge_b850v3_lvds_dp_i2c->irq, NULL,
> +				ge_b850v3_lvds_dp_irq_handler,
> +				IRQF_TRIGGER_HIGH | IRQF_ONESHOT,
> +				"ge-b850v3-lvds-dp", ptn_bridge);
> +		if (ret)
> +			return ret;
> +	}
> +
> +	return 0;
> +}
> +
> +static const struct drm_bridge_funcs ge_b850v3_lvds_dp_funcs = {
> +	.attach = ge_b850v3_lvds_dp_attach,
> +};
> +
> +static int ge_b850v3_lvds_dp_probe(struct i2c_client *ge_b850v3_lvds_dp_i2c,
> +				const struct i2c_device_id *id)
> +{
> +	struct device *dev = &ge_b850v3_lvds_dp_i2c->dev;
> +	struct ge_b850v3_lvds_dp *ptn_bridge;
> +	int ret;
> +	u32 edid_i2c_reg;
> +
> +	ptn_bridge = devm_kzalloc(dev, sizeof(*ptn_bridge), GFP_KERNEL);
> +	if (!ptn_bridge)
> +		return -ENOMEM;
> +
> +	mutex_init(&ptn_bridge->lock);
> +
> +	ptn_bridge->ge_b850v3_lvds_dp_i2c = ge_b850v3_lvds_dp_i2c;
> +	ptn_bridge->bridge.driver_private = ptn_bridge;
> +	i2c_set_clientdata(ge_b850v3_lvds_dp_i2c, ptn_bridge);
> +
> +	ret = of_property_read_u32(dev->of_node, "edid-reg", &edid_i2c_reg);
> +	if (ret) {
> +		dev_err(dev, "edid-reg not specified, aborting...\n");
> +		return -ENODEV;
> +	}
> +
> +	ptn_bridge->edid_i2c = devm_kzalloc(dev,
> +			sizeof(struct i2c_client), GFP_KERNEL);
> +
> +	if (!ptn_bridge->edid_i2c)
> +		return -ENOMEM;
> +
> +	memcpy(ptn_bridge->edid_i2c, ge_b850v3_lvds_dp_i2c,
> +			sizeof(struct i2c_client));
> +
> +	ptn_bridge->edid_i2c->addr = (unsigned short) edid_i2c_reg;
> +
> +	/*
> +	 * Configures the bridge to re-enable interrupts after each ack. As
> +	 * this is the first communication with the chip, fail on error.
> +	 */
> +	ret = i2c_smbus_write_word_data(ge_b850v3_lvds_dp_i2c,
> +			STDP4028_IRQ_OUT_CONF_REG, STDP4028_DPTX_DP_IRQ_EN);
> +	if (ret) {
> +		dev_err(dev, "i2c communication failed, aborting...\n");
> +		return ret;
> +	}
> +
> +	i2c_smbus_write_word_data(ge_b850v3_lvds_dp_i2c,
> +			STDP4028_DPTX_IRQ_EN_REG, STDP4028_DPTX_IRQ_CONFIG);
> +
> +	/* Clear pending interrupts since power up. */
> +	i2c_smbus_write_word_data(ge_b850v3_lvds_dp_i2c,
> +			STDP4028_DPTX_IRQ_STS_REG, STDP4028_DPTX_IRQ_CLEAR);
> +
> +	ptn_bridge->bridge.funcs = &ge_b850v3_lvds_dp_funcs;
> +	ptn_bridge->bridge.of_node = dev->of_node;
> +	ret = drm_bridge_add(&ptn_bridge->bridge);
> +	if (ret) {
> +		DRM_ERROR("Failed to add bridge\n");
> +		return ret;
> +	}
> +
> +	return 0;
> +}
> +
> +static int ge_b850v3_lvds_dp_remove(struct i2c_client *ge_b850v3_lvds_dp_i2c)
> +{
> +	struct ge_b850v3_lvds_dp *ptn_bridge =
> +		i2c_get_clientdata(ge_b850v3_lvds_dp_i2c);
> +
> +	drm_bridge_remove(&ptn_bridge->bridge);
> +
> +	kfree(ptn_bridge->edid);
> +
> +	return 0;
> +}
> +
> +static const struct i2c_device_id ge_b850v3_lvds_dp_i2c_table[] = {
> +	{"b850v3-lvds-dp", 0},
> +	{},
> +};
> +MODULE_DEVICE_TABLE(i2c, ge_b850v3_lvds_dp_i2c_table);
> +
> +static const struct of_device_id ge_b850v3_lvds_dp_match[] = {
> +	{ .compatible = "ge,b850v3-lvds-dp" },
> +	{},
> +};
> +MODULE_DEVICE_TABLE(of, ge_b850v3_lvds_dp_match);
> +
> +static struct i2c_driver ge_b850v3_lvds_dp_driver = {
> +	.id_table	= ge_b850v3_lvds_dp_i2c_table,
> +	.probe		= ge_b850v3_lvds_dp_probe,
> +	.remove		= ge_b850v3_lvds_dp_remove,
> +	.driver		= {
> +		.name		= "b850v3-lvds-dp",
> +		.of_match_table = ge_b850v3_lvds_dp_match,
> +	},
> +};
> +module_i2c_driver(ge_b850v3_lvds_dp_driver);
> +
> +MODULE_AUTHOR("Peter Senna Tschudin <peter.senna@collabora.com>");
> +MODULE_AUTHOR("Martyn Welch <martyn.welch@collabora.co.uk>");
> +MODULE_DESCRIPTION("GE LVDS to DP++ display bridge)");
> +MODULE_LICENSE("GPL v2");
> -- 
> 2.5.5
> 
 
 
 
 

^ permalink raw reply


This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox