diff mbox

[v6] drm: bridge/dw_hdmi: add dw hdmi i2c bus adapter support

Message ID 1472017597-11221-1-git-send-email-vladimir_zapolskiy@mentor.com (mailing list archive)
State New, archived
Headers show

Commit Message

Vladimir Zapolskiy Aug. 24, 2016, 5:46 a.m. UTC
The change adds support of internal HDMI I2C master controller, this
subdevice is used by default, if "ddc-i2c-bus" DT property is omitted.

The main purpose of this functionality is to support reading EDID from
an HDMI monitor on boards, which don't have an I2C bus connected to
DDC pins.

The current implementation does not support "I2C Master Interface
Extended Read Mode" to read data addressed by non-zero segment
pointer, this means that if EDID has more than 1 extension blocks,
EDID reading operation won't succeed, in my practice all tested HDMI
monitors have at maximum one extension block.

Signed-off-by: Vladimir Zapolskiy <vladimir_zapolskiy@mentor.com>
---
The change is based on top of v4.8.0-rc1 and a fix in DW HDMI driver
https://patchwork.kernel.org/patch/9284717/

Changes from v5 to v6:
* rebased on top of v4.8.0-rc1
* fixed one improper resource deallocation on error path of dw_hdmi_bind()
* added a comment describing a mutex asked by checkpatch.pl --strict

Link to version v5: https://patchwork.kernel.org/patch/7279831/

Changes from v4 to v5:
* do I2C bus controller initialization only once in bind() as it was done
  in v1-v3 of the change.

Changes from v3 to v4, thanks to Doug and Philipp for review:
* set speed mode after software reset in dw_hdmi_i2c_init()
* by default set standard speed mode instead of fast speed mode, on iMX6Q
  this configures SCL to 100 KHz, which is compliant with HDMI 1.3a spec
* do I2C bus controller reinitialization on every data transfer,
  this change hopefully solves some observed problems on RK3288 platform
* added short functional change description to dw_hdmi.txt

v3 of the change was

  Tested-by: Philipp Zabel <p.zabel@pengutronix.de>

Changes from v2 to v3, thanks to Russell:
* moved register field value definitions to dw_hdmi.h
* made completions uninterruptible to avoid transfer retries if interrupted
* use one completion for both read and write transfers as in v1,
  operation_reg is removed
* redundant i2c->stat = 0 is removed from dw_hdmi_i2c_read/write()
* struct i2c_algorithm dw_hdmi_algorithm is qualified as const
* dw_hdmi_i2c_adapter() does not modify struct dw_hdmi on error path
* dw_hdmi_i2c_irq() does not modify hdmi->i2c->stat, if interrupt is
  not for I2CM
* spin lock is removed from dw_hdmi_i2c_irq()
* removed spin lock from dw_hdmi_i2c_xfer() around write to
  HDMI_IH_MUTE_I2CM_STAT0 register
* split loop over message array in dw_hdmi_i2c_xfer() to validation
  and transfer parts
* added a mutex to serialize I2C transfer requests, i2c->lock is
  completely removed
* removed if(len) check from dw_hdmi_i2c_write(), hardware supports
  only len>0 transfers
* described extension blocks <= 1 limitation in the commit message
* a number of minor clean ups

Changes from v1 to v2:
* fixed a devm_kfree() signature
* split completions for read and write operations

 .../devicetree/bindings/display/bridge/dw_hdmi.txt |   4 +-
 drivers/gpu/drm/bridge/dw-hdmi.c                   | 265 ++++++++++++++++++++-
 drivers/gpu/drm/bridge/dw-hdmi.h                   |  19 ++
 3 files changed, 281 insertions(+), 7 deletions(-)

Comments

Rob Herring Aug. 29, 2016, 3:50 p.m. UTC | #1
On Wed, Aug 24, 2016 at 08:46:37AM +0300, Vladimir Zapolskiy wrote:
> The change adds support of internal HDMI I2C master controller, this
> subdevice is used by default, if "ddc-i2c-bus" DT property is omitted.
> 
> The main purpose of this functionality is to support reading EDID from
> an HDMI monitor on boards, which don't have an I2C bus connected to
> DDC pins.
> 
> The current implementation does not support "I2C Master Interface
> Extended Read Mode" to read data addressed by non-zero segment
> pointer, this means that if EDID has more than 1 extension blocks,
> EDID reading operation won't succeed, in my practice all tested HDMI
> monitors have at maximum one extension block.
> 
> Signed-off-by: Vladimir Zapolskiy <vladimir_zapolskiy@mentor.com>
> ---
> The change is based on top of v4.8.0-rc1 and a fix in DW HDMI driver
> https://patchwork.kernel.org/patch/9284717/
> 
> Changes from v5 to v6:
> * rebased on top of v4.8.0-rc1
> * fixed one improper resource deallocation on error path of dw_hdmi_bind()
> * added a comment describing a mutex asked by checkpatch.pl --strict
> 
> Link to version v5: https://patchwork.kernel.org/patch/7279831/
> 
> Changes from v4 to v5:
> * do I2C bus controller initialization only once in bind() as it was done
>   in v1-v3 of the change.
> 
> Changes from v3 to v4, thanks to Doug and Philipp for review:
> * set speed mode after software reset in dw_hdmi_i2c_init()
> * by default set standard speed mode instead of fast speed mode, on iMX6Q
>   this configures SCL to 100 KHz, which is compliant with HDMI 1.3a spec
> * do I2C bus controller reinitialization on every data transfer,
>   this change hopefully solves some observed problems on RK3288 platform
> * added short functional change description to dw_hdmi.txt
> 
> v3 of the change was
> 
>   Tested-by: Philipp Zabel <p.zabel@pengutronix.de>
> 
> Changes from v2 to v3, thanks to Russell:
> * moved register field value definitions to dw_hdmi.h
> * made completions uninterruptible to avoid transfer retries if interrupted
> * use one completion for both read and write transfers as in v1,
>   operation_reg is removed
> * redundant i2c->stat = 0 is removed from dw_hdmi_i2c_read/write()
> * struct i2c_algorithm dw_hdmi_algorithm is qualified as const
> * dw_hdmi_i2c_adapter() does not modify struct dw_hdmi on error path
> * dw_hdmi_i2c_irq() does not modify hdmi->i2c->stat, if interrupt is
>   not for I2CM
> * spin lock is removed from dw_hdmi_i2c_irq()
> * removed spin lock from dw_hdmi_i2c_xfer() around write to
>   HDMI_IH_MUTE_I2CM_STAT0 register
> * split loop over message array in dw_hdmi_i2c_xfer() to validation
>   and transfer parts
> * added a mutex to serialize I2C transfer requests, i2c->lock is
>   completely removed
> * removed if(len) check from dw_hdmi_i2c_write(), hardware supports
>   only len>0 transfers
> * described extension blocks <= 1 limitation in the commit message
> * a number of minor clean ups
> 
> Changes from v1 to v2:
> * fixed a devm_kfree() signature
> * split completions for read and write operations
> 
>  .../devicetree/bindings/display/bridge/dw_hdmi.txt |   4 +-

Acked-by: Rob Herring <robh@kernel.org>

>  drivers/gpu/drm/bridge/dw-hdmi.c                   | 265 ++++++++++++++++++++-
>  drivers/gpu/drm/bridge/dw-hdmi.h                   |  19 ++
>  3 files changed, 281 insertions(+), 7 deletions(-)
Emil Velikov Aug. 29, 2016, 4:41 p.m. UTC | #2
Hi all,

On 24 August 2016 at 06:46, Vladimir Zapolskiy
<vladimir_zapolskiy@mentor.com> wrote:

>  MODULE_AUTHOR("Sascha Hauer <s.hauer@pengutronix.de>");
>  MODULE_AUTHOR("Andy Yan <andy.yan@rock-chips.com>");
>  MODULE_AUTHOR("Yakir Yang <ykk@rock-chips.com>");
> +MODULE_AUTHOR("Vladimir Zapolskiy <vladimir_zapolskiy@mentor.com>");
Don't meant to start a flame-war or alike but to educate myself:
Where does one draw the line about adding new author(s) of said
module/subsystem ?

Afaict this is the most common (?) driver in DRM where the list has
grown over time. Should we do the same with others ?

Thanks
Emil
Vladimir Zapolskiy Aug. 29, 2016, 6:49 p.m. UTC | #3
Hi Emil,

On 08/29/2016 07:41 PM, Emil Velikov wrote:
> Hi all,
>
> On 24 August 2016 at 06:46, Vladimir Zapolskiy
> <vladimir_zapolskiy@mentor.com> wrote:
>
>>  MODULE_AUTHOR("Sascha Hauer <s.hauer@pengutronix.de>");
>>  MODULE_AUTHOR("Andy Yan <andy.yan@rock-chips.com>");
>>  MODULE_AUTHOR("Yakir Yang <ykk@rock-chips.com>");
>> +MODULE_AUTHOR("Vladimir Zapolskiy <vladimir_zapolskiy@mentor.com>");
> Don't meant to start a flame-war or alike but to educate myself:
> Where does one draw the line about adding new author(s) of said
> module/subsystem ?

I support you, let's don't sink. Since it evokes questions I'm
ready to provide you with more background information.

> Afaict this is the most common (?) driver in DRM where the list has
> grown over time. Should we do the same with others ?

If you look into the essense of this change it adds support to
an I2C master controller, which is a part of DW HDMI controller.

Originally this particular change was done as a separate driver
in I2C subsystem about 3 years ago and delivered to the customers,
about 2 years ago I published its RFC version:

  RFC:           https://patchwork.ozlabs.org/patch/405100/
  v1 discussion: http://www.mailbrowse.com/linux-i2c/20949.html

As you may see this was a stand-alone driver, which served as
the fourth I2C master controller on iMX6Q, however I believe
it is better to merge two drivers into one. Does it sound like
a good enough reason to merge lists of authors as well?

Hope it clarifies the situation a bit.

--
With best wishes,
Vladimir
Russell King (Oracle) Aug. 30, 2016, 8:12 a.m. UTC | #4
On Mon, Aug 29, 2016 at 05:41:10PM +0100, Emil Velikov wrote:
> Hi all,
> 
> On 24 August 2016 at 06:46, Vladimir Zapolskiy
> <vladimir_zapolskiy@mentor.com> wrote:
> 
> >  MODULE_AUTHOR("Sascha Hauer <s.hauer@pengutronix.de>");
> >  MODULE_AUTHOR("Andy Yan <andy.yan@rock-chips.com>");
> >  MODULE_AUTHOR("Yakir Yang <ykk@rock-chips.com>");
> > +MODULE_AUTHOR("Vladimir Zapolskiy <vladimir_zapolskiy@mentor.com>");
> Don't meant to start a flame-war or alike but to educate myself:
> Where does one draw the line about adding new author(s) of said
> module/subsystem ?
> 
> Afaict this is the most common (?) driver in DRM where the list has
> grown over time. Should we do the same with others ?

... and I'm responsible for just over half the commits in the mainline
kernel and I haven't added myself.  I generally only add myself if I'm
creating new code or been involved in adding new code, I don't add if
I'm merely modifying existing code unless I've replaced a large
quantity of the code.  I think the question people need to ask is:

  "Have I contributed a significant set of changes to be able to claim
   shared authorship of that code?"

You wouldn't claim authorship of a 500 page book if you suggested a
few edits here and there.

Looking at co-authorship in google, I came across:

http://www.southernfriedscience.com/to-co-author-or-not-to-co-author/

which has an interesting list of points on this subject, although more
biased to research papers, which is where this problem normally arises.
That seems to back up my idea of "significant contribution" not just
a few minor changes.

The question then becomes... what is a significant contribution. :)
Emil Velikov Aug. 30, 2016, 11:33 a.m. UTC | #5
On 30 August 2016 at 09:12, Russell King - ARM Linux
<linux@armlinux.org.uk> wrote:
> On Mon, Aug 29, 2016 at 05:41:10PM +0100, Emil Velikov wrote:
>> Hi all,
>>
>> On 24 August 2016 at 06:46, Vladimir Zapolskiy
>> <vladimir_zapolskiy@mentor.com> wrote:
>>
>> >  MODULE_AUTHOR("Sascha Hauer <s.hauer@pengutronix.de>");
>> >  MODULE_AUTHOR("Andy Yan <andy.yan@rock-chips.com>");
>> >  MODULE_AUTHOR("Yakir Yang <ykk@rock-chips.com>");
>> > +MODULE_AUTHOR("Vladimir Zapolskiy <vladimir_zapolskiy@mentor.com>");
>> Don't meant to start a flame-war or alike but to educate myself:
>> Where does one draw the line about adding new author(s) of said
>> module/subsystem ?
>>
>> Afaict this is the most common (?) driver in DRM where the list has
>> grown over time. Should we do the same with others ?
>
> ... and I'm responsible for just over half the commits in the mainline
> kernel and I haven't added myself.  I generally only add myself if I'm
> creating new code or been involved in adding new code, I don't add if
> I'm merely modifying existing code unless I've replaced a large
> quantity of the code.  I think the question people need to ask is:
>
>   "Have I contributed a significant set of changes to be able to claim
>    shared authorship of that code?"
>
> You wouldn't claim authorship of a 500 page book if you suggested a
> few edits here and there.
>
> Looking at co-authorship in google, I came across:
>
> http://www.southernfriedscience.com/to-co-author-or-not-to-co-author/
>
> which has an interesting list of points on this subject, although more
> biased to research papers, which is where this problem normally arises.
> That seems to back up my idea of "significant contribution" not just
> a few minor changes.
>
> The question then becomes... what is a significant contribution. :)
>
Roughly my line of thinking as well.

Considering that the driver has been developed independently in one
shape or another for a few years I think it's perfectly reasonable in
this case.

Thanks for the input all !
Emil
Vladimir Zapolskiy Sept. 1, 2016, 9:06 p.m. UTC | #6
Hello Russell,

On 08/24/2016 08:46 AM, Vladimir Zapolskiy wrote:
> The change adds support of internal HDMI I2C master controller, this
> subdevice is used by default, if "ddc-i2c-bus" DT property is omitted.
>
> The main purpose of this functionality is to support reading EDID from
> an HDMI monitor on boards, which don't have an I2C bus connected to
> DDC pins.
>
> The current implementation does not support "I2C Master Interface
> Extended Read Mode" to read data addressed by non-zero segment
> pointer, this means that if EDID has more than 1 extension blocks,
> EDID reading operation won't succeed, in my practice all tested HDMI
> monitors have at maximum one extension block.
>
> Signed-off-by: Vladimir Zapolskiy <vladimir_zapolskiy@mentor.com>
> ---

do you object to this change?

--
With best wishes,
Vladimir
Vladimir Zapolskiy Sept. 14, 2016, 6:46 p.m. UTC | #7
Hi Philipp,

On 08/29/2016 06:50 PM, Rob Herring wrote:
> On Wed, Aug 24, 2016 at 08:46:37AM +0300, Vladimir Zapolskiy wrote:
>> The change adds support of internal HDMI I2C master controller, this
>> subdevice is used by default, if "ddc-i2c-bus" DT property is omitted.
>>
>> The main purpose of this functionality is to support reading EDID from
>> an HDMI monitor on boards, which don't have an I2C bus connected to
>> DDC pins.
>>
>> The current implementation does not support "I2C Master Interface
>> Extended Read Mode" to read data addressed by non-zero segment
>> pointer, this means that if EDID has more than 1 extension blocks,
>> EDID reading operation won't succeed, in my practice all tested HDMI
>> monitors have at maximum one extension block.
>>
>> Signed-off-by: Vladimir Zapolskiy <vladimir_zapolskiy@mentor.com>
>> ---
>> The change is based on top of v4.8.0-rc1 and a fix in DW HDMI driver
>> https://patchwork.kernel.org/patch/9284717/
>>
>> Changes from v5 to v6:
>> * rebased on top of v4.8.0-rc1
>> * fixed one improper resource deallocation on error path of dw_hdmi_bind()
>> * added a comment describing a mutex asked by checkpatch.pl --strict
>>
>> Link to version v5: https://patchwork.kernel.org/patch/7279831/
>>
>> Changes from v4 to v5:
>> * do I2C bus controller initialization only once in bind() as it was done
>>   in v1-v3 of the change.
>>
>> Changes from v3 to v4, thanks to Doug and Philipp for review:
>> * set speed mode after software reset in dw_hdmi_i2c_init()
>> * by default set standard speed mode instead of fast speed mode, on iMX6Q
>>   this configures SCL to 100 KHz, which is compliant with HDMI 1.3a spec
>> * do I2C bus controller reinitialization on every data transfer,
>>   this change hopefully solves some observed problems on RK3288 platform
>> * added short functional change description to dw_hdmi.txt
>>
>> v3 of the change was
>>
>>   Tested-by: Philipp Zabel <p.zabel@pengutronix.de>
>>
>> Changes from v2 to v3, thanks to Russell:
>> * moved register field value definitions to dw_hdmi.h
>> * made completions uninterruptible to avoid transfer retries if interrupted
>> * use one completion for both read and write transfers as in v1,
>>   operation_reg is removed
>> * redundant i2c->stat = 0 is removed from dw_hdmi_i2c_read/write()
>> * struct i2c_algorithm dw_hdmi_algorithm is qualified as const
>> * dw_hdmi_i2c_adapter() does not modify struct dw_hdmi on error path
>> * dw_hdmi_i2c_irq() does not modify hdmi->i2c->stat, if interrupt is
>>   not for I2CM
>> * spin lock is removed from dw_hdmi_i2c_irq()
>> * removed spin lock from dw_hdmi_i2c_xfer() around write to
>>   HDMI_IH_MUTE_I2CM_STAT0 register
>> * split loop over message array in dw_hdmi_i2c_xfer() to validation
>>   and transfer parts
>> * added a mutex to serialize I2C transfer requests, i2c->lock is
>>   completely removed
>> * removed if(len) check from dw_hdmi_i2c_write(), hardware supports
>>   only len>0 transfers
>> * described extension blocks <= 1 limitation in the commit message
>> * a number of minor clean ups
>>
>> Changes from v1 to v2:
>> * fixed a devm_kfree() signature
>> * split completions for read and write operations
>>
>>  .../devicetree/bindings/display/bridge/dw_hdmi.txt |   4 +-
>
> Acked-by: Rob Herring <robh@kernel.org>
>
>>  drivers/gpu/drm/bridge/dw-hdmi.c                   | 265 ++++++++++++++++++++-
>>  drivers/gpu/drm/bridge/dw-hdmi.h                   |  19 ++
>>  3 files changed, 281 insertions(+), 7 deletions(-)

as far as I know David accepts pull requests from you, can you please
create and send a pull request for v4.9 containing these changes?

    https://patchwork.kernel.org/patch/9284717/ -- with Russell's ack
    https://patchwork.kernel.org/patch/9296883/ -- with Rob's ack and yours tested-by

Some users anticipate this change, for example see https://lkml.org/lkml/2016/9/14/55

--
With best wishes,
Vladimir
Philipp Zabel Sept. 16, 2016, 3:21 p.m. UTC | #8
Hi Vladimir,

Am Mittwoch, den 14.09.2016, 21:46 +0300 schrieb Vladimir Zapolskiy:
> Hi Philipp,
> 
> On 08/29/2016 06:50 PM, Rob Herring wrote:
> > On Wed, Aug 24, 2016 at 08:46:37AM +0300, Vladimir Zapolskiy wrote:
> >> The change adds support of internal HDMI I2C master controller, this
> >> subdevice is used by default, if "ddc-i2c-bus" DT property is omitted.
> >>
> >> The main purpose of this functionality is to support reading EDID from
> >> an HDMI monitor on boards, which don't have an I2C bus connected to
> >> DDC pins.
> >>
> >> The current implementation does not support "I2C Master Interface
> >> Extended Read Mode" to read data addressed by non-zero segment
> >> pointer, this means that if EDID has more than 1 extension blocks,
> >> EDID reading operation won't succeed, in my practice all tested HDMI
> >> monitors have at maximum one extension block.
> >>
> >> Signed-off-by: Vladimir Zapolskiy <vladimir_zapolskiy@mentor.com>
> >> ---
> >> The change is based on top of v4.8.0-rc1 and a fix in DW HDMI driver
> >> https://patchwork.kernel.org/patch/9284717/
> >>
> >> Changes from v5 to v6:
> >> * rebased on top of v4.8.0-rc1
> >> * fixed one improper resource deallocation on error path of dw_hdmi_bind()
> >> * added a comment describing a mutex asked by checkpatch.pl --strict
> >>
> >> Link to version v5: https://patchwork.kernel.org/patch/7279831/
> >>
> >> Changes from v4 to v5:
> >> * do I2C bus controller initialization only once in bind() as it was done
> >>   in v1-v3 of the change.
> >>
> >> Changes from v3 to v4, thanks to Doug and Philipp for review:
> >> * set speed mode after software reset in dw_hdmi_i2c_init()
> >> * by default set standard speed mode instead of fast speed mode, on iMX6Q
> >>   this configures SCL to 100 KHz, which is compliant with HDMI 1.3a spec
> >> * do I2C bus controller reinitialization on every data transfer,
> >>   this change hopefully solves some observed problems on RK3288 platform
> >> * added short functional change description to dw_hdmi.txt
> >>
> >> v3 of the change was
> >>
> >>   Tested-by: Philipp Zabel <p.zabel@pengutronix.de>
> >>
> >> Changes from v2 to v3, thanks to Russell:
> >> * moved register field value definitions to dw_hdmi.h
> >> * made completions uninterruptible to avoid transfer retries if interrupted
> >> * use one completion for both read and write transfers as in v1,
> >>   operation_reg is removed
> >> * redundant i2c->stat = 0 is removed from dw_hdmi_i2c_read/write()
> >> * struct i2c_algorithm dw_hdmi_algorithm is qualified as const
> >> * dw_hdmi_i2c_adapter() does not modify struct dw_hdmi on error path
> >> * dw_hdmi_i2c_irq() does not modify hdmi->i2c->stat, if interrupt is
> >>   not for I2CM
> >> * spin lock is removed from dw_hdmi_i2c_irq()
> >> * removed spin lock from dw_hdmi_i2c_xfer() around write to
> >>   HDMI_IH_MUTE_I2CM_STAT0 register
> >> * split loop over message array in dw_hdmi_i2c_xfer() to validation
> >>   and transfer parts
> >> * added a mutex to serialize I2C transfer requests, i2c->lock is
> >>   completely removed
> >> * removed if(len) check from dw_hdmi_i2c_write(), hardware supports
> >>   only len>0 transfers
> >> * described extension blocks <= 1 limitation in the commit message
> >> * a number of minor clean ups
> >>
> >> Changes from v1 to v2:
> >> * fixed a devm_kfree() signature
> >> * split completions for read and write operations
> >>
> >>  .../devicetree/bindings/display/bridge/dw_hdmi.txt |   4 +-
> >
> > Acked-by: Rob Herring <robh@kernel.org>
> >
> >>  drivers/gpu/drm/bridge/dw-hdmi.c                   | 265 ++++++++++++++++++++-
> >>  drivers/gpu/drm/bridge/dw-hdmi.h                   |  19 ++
> >>  3 files changed, 281 insertions(+), 7 deletions(-)
> 
> as far as I know David accepts pull requests from you, can you please
> create and send a pull request for v4.9 containing these changes?
> 
>     https://patchwork.kernel.org/patch/9284717/ -- with Russell's ack

Is that a forward looking statement? I don't see Russell's ack.

>     https://patchwork.kernel.org/patch/9296883/ -- with Rob's ack and yours tested-by
> 
> Some users anticipate this change, for example see https://lkml.org/lkml/2016/9/14/55

Those I see. I can re-test and prepare a pull request.

regards
Philipp
Vladimir Zapolskiy Sept. 16, 2016, 5:38 p.m. UTC | #9
Hi Philipp,

On 09/16/2016 06:21 PM, Philipp Zabel wrote:
> Hi Vladimir,
>
> Am Mittwoch, den 14.09.2016, 21:46 +0300 schrieb Vladimir Zapolskiy:
>> Hi Philipp,
>>
>> On 08/29/2016 06:50 PM, Rob Herring wrote:
>>> On Wed, Aug 24, 2016 at 08:46:37AM +0300, Vladimir Zapolskiy wrote:
>>>> The change adds support of internal HDMI I2C master controller, this
>>>> subdevice is used by default, if "ddc-i2c-bus" DT property is omitted.
>>>>
>>>> The main purpose of this functionality is to support reading EDID from
>>>> an HDMI monitor on boards, which don't have an I2C bus connected to
>>>> DDC pins.
>>>>
>>>> The current implementation does not support "I2C Master Interface
>>>> Extended Read Mode" to read data addressed by non-zero segment
>>>> pointer, this means that if EDID has more than 1 extension blocks,
>>>> EDID reading operation won't succeed, in my practice all tested HDMI
>>>> monitors have at maximum one extension block.
>>>>
>>>> Signed-off-by: Vladimir Zapolskiy <vladimir_zapolskiy@mentor.com>
>>>> ---
>>>> The change is based on top of v4.8.0-rc1 and a fix in DW HDMI driver
>>>> https://patchwork.kernel.org/patch/9284717/
>>>>
>>>> Changes from v5 to v6:
>>>> * rebased on top of v4.8.0-rc1
>>>> * fixed one improper resource deallocation on error path of dw_hdmi_bind()
>>>> * added a comment describing a mutex asked by checkpatch.pl --strict
>>>>
>>>> Link to version v5: https://patchwork.kernel.org/patch/7279831/
>>>>
>>>> Changes from v4 to v5:
>>>> * do I2C bus controller initialization only once in bind() as it was done
>>>>   in v1-v3 of the change.
>>>>
>>>> Changes from v3 to v4, thanks to Doug and Philipp for review:
>>>> * set speed mode after software reset in dw_hdmi_i2c_init()
>>>> * by default set standard speed mode instead of fast speed mode, on iMX6Q
>>>>   this configures SCL to 100 KHz, which is compliant with HDMI 1.3a spec
>>>> * do I2C bus controller reinitialization on every data transfer,
>>>>   this change hopefully solves some observed problems on RK3288 platform
>>>> * added short functional change description to dw_hdmi.txt
>>>>
>>>> v3 of the change was
>>>>
>>>>   Tested-by: Philipp Zabel <p.zabel@pengutronix.de>
>>>>
>>>> Changes from v2 to v3, thanks to Russell:
>>>> * moved register field value definitions to dw_hdmi.h
>>>> * made completions uninterruptible to avoid transfer retries if interrupted
>>>> * use one completion for both read and write transfers as in v1,
>>>>   operation_reg is removed
>>>> * redundant i2c->stat = 0 is removed from dw_hdmi_i2c_read/write()
>>>> * struct i2c_algorithm dw_hdmi_algorithm is qualified as const
>>>> * dw_hdmi_i2c_adapter() does not modify struct dw_hdmi on error path
>>>> * dw_hdmi_i2c_irq() does not modify hdmi->i2c->stat, if interrupt is
>>>>   not for I2CM
>>>> * spin lock is removed from dw_hdmi_i2c_irq()
>>>> * removed spin lock from dw_hdmi_i2c_xfer() around write to
>>>>   HDMI_IH_MUTE_I2CM_STAT0 register
>>>> * split loop over message array in dw_hdmi_i2c_xfer() to validation
>>>>   and transfer parts
>>>> * added a mutex to serialize I2C transfer requests, i2c->lock is
>>>>   completely removed
>>>> * removed if(len) check from dw_hdmi_i2c_write(), hardware supports
>>>>   only len>0 transfers
>>>> * described extension blocks <= 1 limitation in the commit message
>>>> * a number of minor clean ups
>>>>
>>>> Changes from v1 to v2:
>>>> * fixed a devm_kfree() signature
>>>> * split completions for read and write operations
>>>>
>>>>  .../devicetree/bindings/display/bridge/dw_hdmi.txt |   4 +-
>>>
>>> Acked-by: Rob Herring <robh@kernel.org>
>>>
>>>>  drivers/gpu/drm/bridge/dw-hdmi.c                   | 265 ++++++++++++++++++++-
>>>>  drivers/gpu/drm/bridge/dw-hdmi.h                   |  19 ++
>>>>  3 files changed, 281 insertions(+), 7 deletions(-)
>>
>> as far as I know David accepts pull requests from you, can you please
>> create and send a pull request for v4.9 containing these changes?
>>
>>     https://patchwork.kernel.org/patch/9284717/ -- with Russell's ack
>
> Is that a forward looking statement? I don't see Russell's ack.

Here it is: http://www.spinics.net/lists/dri-devel/msg115880.html

Please don't bother about two other commits from that series, at least
there are official maintainers for the changes. And by the way please
consider to add youself as a maintainer of DW HDMI.

>>     https://patchwork.kernel.org/patch/9296883/ -- with Rob's ack and yours tested-by
>>
>> Some users anticipate this change, for example see https://lkml.org/lkml/2016/9/14/55
>
> Those I see. I can re-test and prepare a pull request.
>

Thank you in advance!

--
With best wishes,
Vladimir
diff mbox

Patch

diff --git a/Documentation/devicetree/bindings/display/bridge/dw_hdmi.txt b/Documentation/devicetree/bindings/display/bridge/dw_hdmi.txt
index dc1452f0d5d8..5e9a84d6e5f1 100644
--- a/Documentation/devicetree/bindings/display/bridge/dw_hdmi.txt
+++ b/Documentation/devicetree/bindings/display/bridge/dw_hdmi.txt
@@ -19,7 +19,9 @@  Required properties:
 
 Optional properties
 - reg-io-width: the width of the reg:1,4, default set to 1 if not present
-- ddc-i2c-bus: phandle of an I2C controller used for DDC EDID probing
+- ddc-i2c-bus: phandle of an I2C controller used for DDC EDID probing,
+  if the property is omitted, a functionally reduced I2C bus
+  controller on DW HDMI is probed
 - clocks, clock-names: phandle to the HDMI CEC clock, name should be "cec"
 
 Example:
diff --git a/drivers/gpu/drm/bridge/dw-hdmi.c b/drivers/gpu/drm/bridge/dw-hdmi.c
index ce3527cd0d25..cdf0a3a2e6f8 100644
--- a/drivers/gpu/drm/bridge/dw-hdmi.c
+++ b/drivers/gpu/drm/bridge/dw-hdmi.c
@@ -1,14 +1,15 @@ 
 /*
+ * DesignWare High-Definition Multimedia Interface (HDMI) driver
+ *
+ * Copyright (C) 2013-2015 Mentor Graphics Inc.
  * Copyright (C) 2011-2013 Freescale Semiconductor, Inc.
+ * Copyright (C) 2010, Guennadi Liakhovetski <g.liakhovetski@gmx.de>
  *
  * This program is free software; you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
  * the Free Software Foundation; either version 2 of the License, or
  * (at your option) any later version.
  *
- * Designware High-Definition Multimedia Interface (HDMI) driver
- *
- * Copyright (C) 2010, Guennadi Liakhovetski <g.liakhovetski@gmx.de>
  */
 #include <linux/module.h>
 #include <linux/irq.h>
@@ -101,6 +102,17 @@  struct hdmi_data_info {
 	struct hdmi_vmode video_mode;
 };
 
+struct dw_hdmi_i2c {
+	struct i2c_adapter	adap;
+
+	struct mutex		lock;	/* used to serialize data transfers */
+	struct completion	cmp;
+	u8			stat;
+
+	u8			slave_reg;
+	bool			is_regaddr;
+};
+
 struct dw_hdmi {
 	struct drm_connector connector;
 	struct drm_encoder *encoder;
@@ -111,6 +123,7 @@  struct dw_hdmi {
 	struct device *dev;
 	struct clk *isfr_clk;
 	struct clk *iahb_clk;
+	struct dw_hdmi_i2c *i2c;
 
 	struct hdmi_data_info hdmi_data;
 	const struct dw_hdmi_plat_data *plat_data;
@@ -198,6 +211,201 @@  static void hdmi_mask_writeb(struct dw_hdmi *hdmi, u8 data, unsigned int reg,
 	hdmi_modb(hdmi, data << shift, mask, reg);
 }
 
+static void dw_hdmi_i2c_init(struct dw_hdmi *hdmi)
+{
+	/* Software reset */
+	hdmi_writeb(hdmi, 0x00, HDMI_I2CM_SOFTRSTZ);
+
+	/* Set Standard Mode speed (determined to be 100KHz on iMX6) */
+	hdmi_writeb(hdmi, 0x00, HDMI_I2CM_DIV);
+
+	/* Set done, not acknowledged and arbitration interrupt polarities */
+	hdmi_writeb(hdmi, HDMI_I2CM_INT_DONE_POL, HDMI_I2CM_INT);
+	hdmi_writeb(hdmi, HDMI_I2CM_CTLINT_NAC_POL | HDMI_I2CM_CTLINT_ARB_POL,
+		    HDMI_I2CM_CTLINT);
+
+	/* Clear DONE and ERROR interrupts */
+	hdmi_writeb(hdmi, HDMI_IH_I2CM_STAT0_ERROR | HDMI_IH_I2CM_STAT0_DONE,
+		    HDMI_IH_I2CM_STAT0);
+
+	/* Mute DONE and ERROR interrupts */
+	hdmi_writeb(hdmi, HDMI_IH_I2CM_STAT0_ERROR | HDMI_IH_I2CM_STAT0_DONE,
+		    HDMI_IH_MUTE_I2CM_STAT0);
+}
+
+static int dw_hdmi_i2c_read(struct dw_hdmi *hdmi,
+			    unsigned char *buf, unsigned int length)
+{
+	struct dw_hdmi_i2c *i2c = hdmi->i2c;
+	int stat;
+
+	if (!i2c->is_regaddr) {
+		dev_dbg(hdmi->dev, "set read register address to 0\n");
+		i2c->slave_reg = 0x00;
+		i2c->is_regaddr = true;
+	}
+
+	while (length--) {
+		reinit_completion(&i2c->cmp);
+
+		hdmi_writeb(hdmi, i2c->slave_reg++, HDMI_I2CM_ADDRESS);
+		hdmi_writeb(hdmi, HDMI_I2CM_OPERATION_READ,
+			    HDMI_I2CM_OPERATION);
+
+		stat = wait_for_completion_timeout(&i2c->cmp, HZ / 10);
+		if (!stat)
+			return -EAGAIN;
+
+		/* Check for error condition on the bus */
+		if (i2c->stat & HDMI_IH_I2CM_STAT0_ERROR)
+			return -EIO;
+
+		*buf++ = hdmi_readb(hdmi, HDMI_I2CM_DATAI);
+	}
+
+	return 0;
+}
+
+static int dw_hdmi_i2c_write(struct dw_hdmi *hdmi,
+			     unsigned char *buf, unsigned int length)
+{
+	struct dw_hdmi_i2c *i2c = hdmi->i2c;
+	int stat;
+
+	if (!i2c->is_regaddr) {
+		/* Use the first write byte as register address */
+		i2c->slave_reg = buf[0];
+		length--;
+		buf++;
+		i2c->is_regaddr = true;
+	}
+
+	while (length--) {
+		reinit_completion(&i2c->cmp);
+
+		hdmi_writeb(hdmi, *buf++, HDMI_I2CM_DATAO);
+		hdmi_writeb(hdmi, i2c->slave_reg++, HDMI_I2CM_ADDRESS);
+		hdmi_writeb(hdmi, HDMI_I2CM_OPERATION_WRITE,
+			    HDMI_I2CM_OPERATION);
+
+		stat = wait_for_completion_timeout(&i2c->cmp, HZ / 10);
+		if (!stat)
+			return -EAGAIN;
+
+		/* Check for error condition on the bus */
+		if (i2c->stat & HDMI_IH_I2CM_STAT0_ERROR)
+			return -EIO;
+	}
+
+	return 0;
+}
+
+static int dw_hdmi_i2c_xfer(struct i2c_adapter *adap,
+			    struct i2c_msg *msgs, int num)
+{
+	struct dw_hdmi *hdmi = i2c_get_adapdata(adap);
+	struct dw_hdmi_i2c *i2c = hdmi->i2c;
+	u8 addr = msgs[0].addr;
+	int i, ret = 0;
+
+	dev_dbg(hdmi->dev, "xfer: num: %d, addr: %#x\n", num, addr);
+
+	for (i = 0; i < num; i++) {
+		if (msgs[i].addr != addr) {
+			dev_warn(hdmi->dev,
+				 "unsupported transfer, changed slave address\n");
+			return -EOPNOTSUPP;
+		}
+
+		if (msgs[i].len == 0) {
+			dev_dbg(hdmi->dev,
+				"unsupported transfer %d/%d, no data\n",
+				i + 1, num);
+			return -EOPNOTSUPP;
+		}
+	}
+
+	mutex_lock(&i2c->lock);
+
+	/* Unmute DONE and ERROR interrupts */
+	hdmi_writeb(hdmi, 0x00, HDMI_IH_MUTE_I2CM_STAT0);
+
+	/* Set slave device address taken from the first I2C message */
+	hdmi_writeb(hdmi, addr, HDMI_I2CM_SLAVE);
+
+	/* Set slave device register address on transfer */
+	i2c->is_regaddr = false;
+
+	for (i = 0; i < num; i++) {
+		dev_dbg(hdmi->dev, "xfer: num: %d/%d, len: %d, flags: %#x\n",
+			i + 1, num, msgs[i].len, msgs[i].flags);
+
+		if (msgs[i].flags & I2C_M_RD)
+			ret = dw_hdmi_i2c_read(hdmi, msgs[i].buf, msgs[i].len);
+		else
+			ret = dw_hdmi_i2c_write(hdmi, msgs[i].buf, msgs[i].len);
+
+		if (ret < 0)
+			break;
+	}
+
+	if (!ret)
+		ret = num;
+
+	/* Mute DONE and ERROR interrupts */
+	hdmi_writeb(hdmi, HDMI_IH_I2CM_STAT0_ERROR | HDMI_IH_I2CM_STAT0_DONE,
+		    HDMI_IH_MUTE_I2CM_STAT0);
+
+	mutex_unlock(&i2c->lock);
+
+	return ret;
+}
+
+static u32 dw_hdmi_i2c_func(struct i2c_adapter *adapter)
+{
+	return I2C_FUNC_I2C | I2C_FUNC_SMBUS_EMUL;
+}
+
+static const struct i2c_algorithm dw_hdmi_algorithm = {
+	.master_xfer	= dw_hdmi_i2c_xfer,
+	.functionality	= dw_hdmi_i2c_func,
+};
+
+static struct i2c_adapter *dw_hdmi_i2c_adapter(struct dw_hdmi *hdmi)
+{
+	struct i2c_adapter *adap;
+	struct dw_hdmi_i2c *i2c;
+	int ret;
+
+	i2c = devm_kzalloc(hdmi->dev, sizeof(*i2c), GFP_KERNEL);
+	if (!i2c)
+		return ERR_PTR(-ENOMEM);
+
+	mutex_init(&i2c->lock);
+	init_completion(&i2c->cmp);
+
+	adap = &i2c->adap;
+	adap->class = I2C_CLASS_DDC;
+	adap->owner = THIS_MODULE;
+	adap->dev.parent = hdmi->dev;
+	adap->algo = &dw_hdmi_algorithm;
+	strlcpy(adap->name, "DesignWare HDMI", sizeof(adap->name));
+	i2c_set_adapdata(adap, hdmi);
+
+	ret = i2c_add_adapter(adap);
+	if (ret) {
+		dev_warn(hdmi->dev, "cannot add %s I2C adapter\n", adap->name);
+		devm_kfree(hdmi->dev, i2c);
+		return ERR_PTR(ret);
+	}
+
+	hdmi->i2c = i2c;
+
+	dev_info(hdmi->dev, "registered %s I2C bus driver\n", adap->name);
+
+	return adap;
+}
+
 static void hdmi_set_cts_n(struct dw_hdmi *hdmi, unsigned int cts,
 			   unsigned int n)
 {
@@ -1517,16 +1725,40 @@  static const struct drm_bridge_funcs dw_hdmi_bridge_funcs = {
 	.mode_set = dw_hdmi_bridge_mode_set,
 };
 
+static irqreturn_t dw_hdmi_i2c_irq(struct dw_hdmi *hdmi)
+{
+	struct dw_hdmi_i2c *i2c = hdmi->i2c;
+	unsigned int stat;
+
+	stat = hdmi_readb(hdmi, HDMI_IH_I2CM_STAT0);
+	if (!stat)
+		return IRQ_NONE;
+
+	hdmi_writeb(hdmi, stat, HDMI_IH_I2CM_STAT0);
+
+	i2c->stat = stat;
+
+	complete(&i2c->cmp);
+
+	return IRQ_HANDLED;
+}
+
 static irqreturn_t dw_hdmi_hardirq(int irq, void *dev_id)
 {
 	struct dw_hdmi *hdmi = dev_id;
 	u8 intr_stat;
+	irqreturn_t ret = IRQ_NONE;
+
+	if (hdmi->i2c)
+		ret = dw_hdmi_i2c_irq(hdmi);
 
 	intr_stat = hdmi_readb(hdmi, HDMI_IH_PHY_STAT0);
-	if (intr_stat)
+	if (intr_stat) {
 		hdmi_writeb(hdmi, ~0, HDMI_IH_MUTE_PHY_STAT0);
+		return IRQ_WAKE_THREAD;
+	}
 
-	return intr_stat ? IRQ_WAKE_THREAD : IRQ_NONE;
+	return ret;
 }
 
 static irqreturn_t dw_hdmi_irq(int irq, void *dev_id)
@@ -1751,6 +1983,13 @@  int dw_hdmi_bind(struct device *dev, struct device *master,
 	 */
 	hdmi_init_clk_regenerator(hdmi);
 
+	/* If DDC bus is not specified, try to register HDMI I2C bus */
+	if (!hdmi->ddc) {
+		hdmi->ddc = dw_hdmi_i2c_adapter(hdmi);
+		if (IS_ERR(hdmi->ddc))
+			hdmi->ddc = NULL;
+	}
+
 	/*
 	 * Configure registers related to HDMI interrupt
 	 * generation before registering IRQ.
@@ -1791,11 +2030,20 @@  int dw_hdmi_bind(struct device *dev, struct device *master,
 		hdmi->audio = platform_device_register_full(&pdevinfo);
 	}
 
+	/* Reset HDMI DDC I2C master controller and mute I2CM interrupts */
+	if (hdmi->i2c)
+		dw_hdmi_i2c_init(hdmi);
+
 	dev_set_drvdata(dev, hdmi);
 
 	return 0;
 
 err_iahb:
+	if (hdmi->i2c) {
+		i2c_del_adapter(&hdmi->i2c->adap);
+		hdmi->ddc = NULL;
+	}
+
 	clk_disable_unprepare(hdmi->iahb_clk);
 err_isfr:
 	clk_disable_unprepare(hdmi->isfr_clk);
@@ -1821,13 +2069,18 @@  void dw_hdmi_unbind(struct device *dev, struct device *master, void *data)
 
 	clk_disable_unprepare(hdmi->iahb_clk);
 	clk_disable_unprepare(hdmi->isfr_clk);
-	i2c_put_adapter(hdmi->ddc);
+
+	if (hdmi->i2c)
+		i2c_del_adapter(&hdmi->i2c->adap);
+	else
+		i2c_put_adapter(hdmi->ddc);
 }
 EXPORT_SYMBOL_GPL(dw_hdmi_unbind);
 
 MODULE_AUTHOR("Sascha Hauer <s.hauer@pengutronix.de>");
 MODULE_AUTHOR("Andy Yan <andy.yan@rock-chips.com>");
 MODULE_AUTHOR("Yakir Yang <ykk@rock-chips.com>");
+MODULE_AUTHOR("Vladimir Zapolskiy <vladimir_zapolskiy@mentor.com>");
 MODULE_DESCRIPTION("DW HDMI transmitter driver");
 MODULE_LICENSE("GPL");
 MODULE_ALIAS("platform:dw-hdmi");
diff --git a/drivers/gpu/drm/bridge/dw-hdmi.h b/drivers/gpu/drm/bridge/dw-hdmi.h
index fc9a560429d6..6aadc840e888 100644
--- a/drivers/gpu/drm/bridge/dw-hdmi.h
+++ b/drivers/gpu/drm/bridge/dw-hdmi.h
@@ -566,6 +566,10 @@  enum {
 	HDMI_IH_PHY_STAT0_TX_PHY_LOCK = 0x2,
 	HDMI_IH_PHY_STAT0_HPD = 0x1,
 
+/* IH_I2CM_STAT0 and IH_MUTE_I2CM_STAT0 field values */
+	HDMI_IH_I2CM_STAT0_DONE = 0x2,
+	HDMI_IH_I2CM_STAT0_ERROR = 0x1,
+
 /* IH_MUTE_I2CMPHY_STAT0 field values */
 	HDMI_IH_MUTE_I2CMPHY_STAT0_I2CMPHYDONE = 0x2,
 	HDMI_IH_MUTE_I2CMPHY_STAT0_I2CMPHYERROR = 0x1,
@@ -1032,6 +1036,21 @@  enum {
 	HDMI_A_VIDPOLCFG_HSYNCPOL_MASK = 0x2,
 	HDMI_A_VIDPOLCFG_HSYNCPOL_ACTIVE_HIGH = 0x2,
 	HDMI_A_VIDPOLCFG_HSYNCPOL_ACTIVE_LOW = 0x0,
+
+/* I2CM_OPERATION field values */
+	HDMI_I2CM_OPERATION_WRITE = 0x10,
+	HDMI_I2CM_OPERATION_READ_EXT = 0x2,
+	HDMI_I2CM_OPERATION_READ = 0x1,
+
+/* I2CM_INT field values */
+	HDMI_I2CM_INT_DONE_POL = 0x8,
+	HDMI_I2CM_INT_DONE_MASK = 0x4,
+
+/* I2CM_CTLINT field values */
+	HDMI_I2CM_CTLINT_NAC_POL = 0x80,
+	HDMI_I2CM_CTLINT_NAC_MASK = 0x40,
+	HDMI_I2CM_CTLINT_ARB_POL = 0x8,
+	HDMI_I2CM_CTLINT_ARB_MASK = 0x4,
 };
 
 #endif /* __DW_HDMI_H__ */