diff mbox series

[net-next,v3,06/12] net: ethernet: oa_tc6: implement internal PHY initialization

Message ID 20240306085017.21731-7-Parthiban.Veerasooran@microchip.com (mailing list archive)
State Changes Requested
Delegated to: Netdev Maintainers
Headers show
Series Add support for OPEN Alliance 10BASE-T1x MACPHY Serial Interface | expand

Checks

Context Check Description
netdev/series_format success Posting correctly formatted
netdev/tree_selection success Clearly marked for net-next
netdev/ynl success Generated files up to date; no warnings/errors; no diff in generated;
netdev/fixes_present success Fixes tag not required for -next series
netdev/header_inline success No static functions without inline keyword in header files
netdev/build_32bit success Errors and warnings before: 940 this patch: 940
netdev/build_tools success Errors and warnings before: 0 this patch: 0
netdev/cc_maintainers warning 1 maintainers not CCed: parthiban.veerasooran@microchip.com
netdev/build_clang success Errors and warnings before: 956 this patch: 956
netdev/verify_signedoff success Signed-off-by tag matches author and committer
netdev/deprecated_api success None detected
netdev/check_selftest success No net selftest shell script
netdev/verify_fixes success No Fixes tag
netdev/build_allmodconfig_warn success Errors and warnings before: 956 this patch: 956
netdev/checkpatch warning WARNING: line length of 81 exceeds 80 columns
netdev/build_clang_rust success No Rust files in patch. Skipping build
netdev/kdoc success Errors and warnings before: 0 this patch: 0
netdev/source_inline success Was 0 now: 0
netdev/contest success net-next-2024-03-06--15-00 (tests: 891)

Commit Message

Parthiban Veerasooran March 6, 2024, 8:50 a.m. UTC
Internal PHY is initialized as per the PHY register capability supported
by the MAC-PHY. Direct PHY Register Access Capability indicates if PHY
registers are directly accessible within the SPI register memory space.
Indirect PHY Register Access Capability indicates if PHY registers are
indirectly accessible through the MDIO/MDC registers MDIOACCn defined in
OPEN Alliance specification. Currently the direct register access is only
supported.

Signed-off-by: Parthiban Veerasooran <Parthiban.Veerasooran@microchip.com>
---
 drivers/net/ethernet/oa_tc6.c | 160 +++++++++++++++++++++++++++++++++-
 include/linux/oa_tc6.h        |   4 +-
 2 files changed, 162 insertions(+), 2 deletions(-)

Comments

Andrew Lunn March 7, 2024, 1:13 a.m. UTC | #1
> +/* PHY Clause 22 and 29 registers base address and mask */
> +#define OA_TC6_PHY_STD_REG_ADDR_BASE		0xFF00
> +#define OA_TC6_PHY_STD_REG_ADDR_MASK		0x3F

[Goes and looks a 802.3]

Clause 29 is "System considerations for multisegment 100BASE-T networks"

I don't see any mention of registers in there.

TC6 says:

"Clause 22 standard registers and Clause 22 extended registers (Clause
29) are directly mapped into MMS 0 as shown in Table 7."

Going back to 802.3, we have 22.2.4:

The MII basic register set consists of two registers referred to as
the Control register (Register 0) and the Status register (Register
1). All PHYs that provide an MII Management Interface shall
incorporate the basic register set. All PHYs that provide a GMII shall
incorporate an extended basic register set consisting of the Control
register (Register 0), Status register (Register 1), and Extended
Status register (Register 15). The status and control functions
defined here are considered basic and fundamental to 100 Mb/s and 1000
Mb/s PHYs. Registers 2 through 14 are part of the extended register
set. The format of Registers 4 through 10 are defined for the specific
Auto-Negotiation protocol used (Clause 28 or Clause 37). The format of
these registers is selected by the bit settings of Registers 1 and 15.

So clause 29 is not making much sense here. Can anybody explain it?

> +static int oa_tc6_mdiobus_register(struct oa_tc6 *tc6)
> +{
> +	int ret;
> +
> +	tc6->mdiobus = mdiobus_alloc();
> +	if (!tc6->mdiobus) {
> +		netdev_err(tc6->netdev, "MDIO bus alloc failed\n");
> +		return -ENODEV;
> +	}
> +
> +	tc6->mdiobus->priv = tc6;
> +	tc6->mdiobus->read = oa_tc6_mdiobus_direct_read;
> +	tc6->mdiobus->write = oa_tc6_mdiobus_direct_write;

This might get answered in later patches. PLCA registers are in C45
address space, VEND1 if i remember correctly. You don't provide any
C45 access methods here. Does TC6 specify that C45 over C22 must be
implemented?

The standard does say:

Vendor specific registers may be mapped into MMS 10 though MMS
15. When directly mapped, PHY vendor specific registers in MMD 30 or
MMD 31 would be mapped into the vendor specific MMS 10 through MMS 15.

So i'm thinking you might need to provide C45 access, at least MMD 30,
via MMS 10-15?

    Andrew
Parthiban Veerasooran March 7, 2024, 2:41 p.m. UTC | #2
Hi Andrew,

On 07/03/24 6:43 am, Andrew Lunn wrote:
> EXTERNAL EMAIL: Do not click links or open attachments unless you know the content is safe
> 
>> +/* PHY Clause 22 and 29 registers base address and mask */
>> +#define OA_TC6_PHY_STD_REG_ADDR_BASE         0xFF00
>> +#define OA_TC6_PHY_STD_REG_ADDR_MASK         0x3F
> 
> [Goes and looks a 802.3]
> 
> Clause 29 is "System considerations for multisegment 100BASE-T networks"
> 
> I don't see any mention of registers in there.
> 
> TC6 says:
> 
> "Clause 22 standard registers and Clause 22 extended registers (Clause
> 29) are directly mapped into MMS 0 as shown in Table 7."
> 
> Going back to 802.3, we have 22.2.4:
> 
> The MII basic register set consists of two registers referred to as
> the Control register (Register 0) and the Status register (Register
> 1). All PHYs that provide an MII Management Interface shall
> incorporate the basic register set. All PHYs that provide a GMII shall
> incorporate an extended basic register set consisting of the Control
> register (Register 0), Status register (Register 1), and Extended
> Status register (Register 15). The status and control functions
> defined here are considered basic and fundamental to 100 Mb/s and 1000
> Mb/s PHYs. Registers 2 through 14 are part of the extended register
> set. The format of Registers 4 through 10 are defined for the specific
> Auto-Negotiation protocol used (Clause 28 or Clause 37). The format of
> these registers is selected by the bit settings of Registers 1 and 15.
> 
> So clause 29 is not making much sense here. Can anybody explain it?
> 
>> +static int oa_tc6_mdiobus_register(struct oa_tc6 *tc6)
>> +{
>> +     int ret;
>> +
>> +     tc6->mdiobus = mdiobus_alloc();
>> +     if (!tc6->mdiobus) {
>> +             netdev_err(tc6->netdev, "MDIO bus alloc failed\n");
>> +             return -ENODEV;
>> +     }
>> +
>> +     tc6->mdiobus->priv = tc6;
>> +     tc6->mdiobus->read = oa_tc6_mdiobus_direct_read;
>> +     tc6->mdiobus->write = oa_tc6_mdiobus_direct_write;
> 
> This might get answered in later patches. PLCA registers are in C45
> address space, VEND1 if i remember correctly. You don't provide any
> C45 access methods here. Does TC6 specify that C45 over C22 must be
> implemented?
No the spec doesn't say anything like this. But, as C22 registers are 
mapped in the MMS 0, registers 0xD and 0xE can be used to access C45 
registers indirectly. That's why the driver implemented the above 
functions. I agree that indirect access is slower and requires more 
control commands than direct access. So implementing the direct access 
of C45 registers will overcome this issue.
> 
> The standard does say:
> 
> Vendor specific registers may be mapped into MMS 10 though MMS
> 15. When directly mapped, PHY vendor specific registers in MMD 30 or
> MMD 31 would be mapped into the vendor specific MMS 10 through MMS 15.
> 
> So i'm thinking you might need to provide C45 access, at least MMD 30,
> via MMS 10-15?
Thanks for this detailed comment. If understand you correctly by 
consolidating all your above explanations, the driver should provide C45 
access to the PHY vendor specific and PLCA registers (MMD 31). As per 
the specification, Table 6 describes the Register Memory Map Selector 
(MMS) Assignment. In this, MMS 4 maps the PHY vendor specific and PLCA 
registers. They are in the MMD 31 address space as per spec. They can be 
directly accessed using read_c45 and write_c45 functions in the mdio bus.

In Microchip's MAC-PHY (LAN8650), PHY – Vendor Specific and PLCA 
Registers (MMD 31) mapped in the MMS 4 as per the table 6 in the spec.
There is no other PHY vendor specific registers are mapped in the MMS 10 
through 15. No idea whether any other vendor's MAC-PHY uses MMS 10 
through 15 to map PHY – Vendor Specific and PLCA Registers (MMD 31).

I have given the code below for the C45 access methods. Kindly check is 
this something you expected?

--- Code starts ---

/* PHY – Vendor Specific and PLCA Registers (MMD 31) */ 

#define OA_TC6_PHY_VS_PLCA_REG_ADDR_BASE        0x40000
,,,

static int oa_tc6_mdiobus_read_c45(struct mii_bus *bus, int addr, int 
devnum, int regnum)
{ 

         struct oa_tc6 *tc6 = bus->priv; 

         u32 regval; 

         bool ret; 

 

         ret = oa_tc6_read_register(tc6, 
OA_TC6_PHY_VS_PLCA_REG_ADDR_BASE | regnum, &regval); 

         if (ret) 

                 return -ENODEV; 

 

         return regval; 

} 

 

static int oa_tc6_mdiobus_write_c45(struct mii_bus *bus, int addr, int 
devnum, int regnum, u16 val)
{ 

         struct oa_tc6 *tc6 = bus->priv; 

 

         return oa_tc6_write_register(tc6, 
OA_TC6_PHY_VS_PLCA_REG_ADDR_BASE | regnum, val); 

}
,,,

tc6->mdiobus->read_c45 = oa_tc6_mdiobus_read_c45;
tc6->mdiobus->write_c45 = oa_tc6_mdiobus_write_c45;

--- Code ends ---

Best regrads,
Parthiban V

> 
>      Andrew
>
Andrew Lunn March 7, 2024, 4:36 p.m. UTC | #3
> >> +static int oa_tc6_mdiobus_register(struct oa_tc6 *tc6)
> >> +{
> >> +     int ret;
> >> +
> >> +     tc6->mdiobus = mdiobus_alloc();
> >> +     if (!tc6->mdiobus) {
> >> +             netdev_err(tc6->netdev, "MDIO bus alloc failed\n");
> >> +             return -ENODEV;
> >> +     }
> >> +
> >> +     tc6->mdiobus->priv = tc6;
> >> +     tc6->mdiobus->read = oa_tc6_mdiobus_direct_read;
> >> +     tc6->mdiobus->write = oa_tc6_mdiobus_direct_write;
> > 
> > This might get answered in later patches. PLCA registers are in C45
> > address space, VEND1 if i remember correctly. You don't provide any
> > C45 access methods here. Does TC6 specify that C45 over C22 must be
> > implemented?
> No the spec doesn't say anything like this. But, as C22 registers are 
> mapped in the MMS 0, registers 0xD and 0xE can be used to access C45 
> registers indirectly. That's why the driver implemented the above 
> functions. I agree that indirect access is slower and requires more 
> control commands than direct access. So implementing the direct access 
> of C45 registers will overcome this issue.

It is not just about performance. It is about compliance to the
standard. The standard does not say anything about C45 over C22. So
there is no reason to expect a PHY device to implement it. It might,
but its optional.

> > The standard does say:
> > 
> > Vendor specific registers may be mapped into MMS 10 though MMS
> > 15. When directly mapped, PHY vendor specific registers in MMD 30 or
> > MMD 31 would be mapped into the vendor specific MMS 10 through MMS 15.
> > 
> > So i'm thinking you might need to provide C45 access, at least MMD 30,
> > via MMS 10-15?
> Thanks for this detailed comment. If understand you correctly by 
> consolidating all your above explanations, the driver should provide C45 
> access to the PHY vendor specific and PLCA registers (MMD 31). As per 
> the specification, Table 6 describes the Register Memory Map Selector 
> (MMS) Assignment. In this, MMS 4 maps the PHY vendor specific and PLCA 
> registers. They are in the MMD 31 address space as per spec. They can be 
> directly accessed using read_c45 and write_c45 functions in the mdio bus.

Yes. I think this is required to conform to the standard.

> In Microchip's MAC-PHY (LAN8650), PHY – Vendor Specific and PLCA 
> Registers (MMD 31) mapped in the MMS 4 as per the table 6 in the spec.
> There is no other PHY vendor specific registers are mapped in the MMS 10 
> through 15. No idea whether any other vendor's MAC-PHY uses MMS 10 
> through 15 to map PHY – Vendor Specific and PLCA Registers (MMD 31).
> 
> I have given the code below for the C45 access methods. Kindly check is 
> this something you expected?

The code got mangled by your mail client :-(

> --- Code starts ---
> 
> /* PHY – Vendor Specific and PLCA Registers (MMD 31) */ 
> 
> #define OA_TC6_PHY_VS_PLCA_REG_ADDR_BASE        0x40000
> ,,,
> 
> static int oa_tc6_mdiobus_read_c45(struct mii_bus *bus, int addr, int 
> devnum, int regnum)
> { 
> 
>          struct oa_tc6 *tc6 = bus->priv; 
> 
>          u32 regval; 
> 
>          bool ret; 
> 
>  
> 
>          ret = oa_tc6_read_register(tc6, 
> OA_TC6_PHY_VS_PLCA_REG_ADDR_BASE | regnum, &regval); 

You appear to ignore devnum. I don't think you can do that. The core
phylib code might try to access other MMDs, e.g. it might try to see
if EEE is supported, by reading MDIO_MMD_PCS, MDIO_PCS_EEE_ABLE.

	Andrew
Parthiban Veerasooran March 8, 2024, 12:05 p.m. UTC | #4
Hi Andrew,

On 07/03/24 10:06 pm, Andrew Lunn wrote:
> EXTERNAL EMAIL: Do not click links or open attachments unless you know the content is safe
> 
>>>> +static int oa_tc6_mdiobus_register(struct oa_tc6 *tc6)
>>>> +{
>>>> +     int ret;
>>>> +
>>>> +     tc6->mdiobus = mdiobus_alloc();
>>>> +     if (!tc6->mdiobus) {
>>>> +             netdev_err(tc6->netdev, "MDIO bus alloc failed\n");
>>>> +             return -ENODEV;
>>>> +     }
>>>> +
>>>> +     tc6->mdiobus->priv = tc6;
>>>> +     tc6->mdiobus->read = oa_tc6_mdiobus_direct_read;
>>>> +     tc6->mdiobus->write = oa_tc6_mdiobus_direct_write;
>>>
>>> This might get answered in later patches. PLCA registers are in C45
>>> address space, VEND1 if i remember correctly. You don't provide any
>>> C45 access methods here. Does TC6 specify that C45 over C22 must be
>>> implemented?
>> No the spec doesn't say anything like this. But, as C22 registers are
>> mapped in the MMS 0, registers 0xD and 0xE can be used to access C45
>> registers indirectly. That's why the driver implemented the above
>> functions. I agree that indirect access is slower and requires more
>> control commands than direct access. So implementing the direct access
>> of C45 registers will overcome this issue.
> 
> It is not just about performance. It is about compliance to the
> standard. The standard does not say anything about C45 over C22. So
> there is no reason to expect a PHY device to implement it. It might,
> but its optional.
Yes, understood.
> 
>>> The standard does say:
>>>
>>> Vendor specific registers may be mapped into MMS 10 though MMS
>>> 15. When directly mapped, PHY vendor specific registers in MMD 30 or
>>> MMD 31 would be mapped into the vendor specific MMS 10 through MMS 15.
>>>
>>> So i'm thinking you might need to provide C45 access, at least MMD 30,
>>> via MMS 10-15?
>> Thanks for this detailed comment. If understand you correctly by
>> consolidating all your above explanations, the driver should provide C45
>> access to the PHY vendor specific and PLCA registers (MMD 31). As per
>> the specification, Table 6 describes the Register Memory Map Selector
>> (MMS) Assignment. In this, MMS 4 maps the PHY vendor specific and PLCA
>> registers. They are in the MMD 31 address space as per spec. They can be
>> directly accessed using read_c45 and write_c45 functions in the mdio bus.
> 
> Yes. I think this is required to conform to the standard.
Ok then let's implement like below.
> 
>> In Microchip's MAC-PHY (LAN8650), PHY – Vendor Specific and PLCA
>> Registers (MMD 31) mapped in the MMS 4 as per the table 6 in the spec.
>> There is no other PHY vendor specific registers are mapped in the MMS 10
>> through 15. No idea whether any other vendor's MAC-PHY uses MMS 10
>> through 15 to map PHY – Vendor Specific and PLCA Registers (MMD 31).
>>
>> I have given the code below for the C45 access methods. Kindly check is
>> this something you expected?
> 
> The code got mangled by your mail client :-(
Oh sorry.
> 
>> --- Code starts ---
>>
>> /* PHY – Vendor Specific and PLCA Registers (MMD 31) */
>>
>> #define OA_TC6_PHY_VS_PLCA_REG_ADDR_BASE        0x40000
>> ,,,
>>
>> static int oa_tc6_mdiobus_read_c45(struct mii_bus *bus, int addr, int
>> devnum, int regnum)
>> {
>>
>>           struct oa_tc6 *tc6 = bus->priv;
>>
>>           u32 regval;
>>
>>           bool ret;
>>
>>
>>
>>           ret = oa_tc6_read_register(tc6,
>> OA_TC6_PHY_VS_PLCA_REG_ADDR_BASE | regnum, &regval);
> 
> You appear to ignore devnum. I don't think you can do that. The core
> phylib code might try to access other MMDs, e.g. it might try to see
> if EEE is supported, by reading MDIO_MMD_PCS, MDIO_PCS_EEE_ABLE.
Ok, as per the table 6 in the spec, PHY C45 registers are mapped in the 
MMS like below,

PHY – PCS Registers (MMD 3)  --->  MMS 2
PHY – PMA/PMD Registers (MMD 1)  --->   MMS 3
PHY – Vendor Specific and PLCA Registers (MMD 31)  --->  MMS 4
PHY – Auto-Negotiation Registers (MMD 7)  --->  MMS 5
PHY – Power Unit (MMD 13)  --->  MMS 6

MMD 13 for PHY - Power Unit is not defined in the mdio.h. So in the 
below code I have defined it locally (MDIO_MMD_POWER_UNIT). May be 
needed to do this in the mdio.h file when coming to this patch.

https://elixir.bootlin.com/linux/v6.8-rc7/source/include/uapi/linux/mdio.h

Hope you are expecting like below? I believe this time the code will not 
get mangled. If happens then sorry for that.

--- Code starts here ---

/* PHY – Clause 45 registers memory map selector (MMS) as per table 6 in 
the OPEN Alliance specification.
  */
#define OA_TC6_PHY_PCS_MMS2                     2       /* MMD 3 */
#define OA_TC6_PHY_PMA_PMD_MMS3                 3       /* MMD 1 */
#define OA_TC6_PHY_VS_PLCA_MMS4                 4       /* MMD 31 */
#define OA_TC6_PHY_AUTO_NEG_MMS5                5       /* MMD 7 */
#define OA_TC6_PHY_POWER_UNIT_MMS6              6       /* MMD 13 */

/* MDIO Manageable Device (MMD) for PHY Power Unit */
#define MDIO_MMD_POWER_UNIT                     13      /* PHY Power Unit */

static int oa_tc6_mdiobus_read_c45(struct mii_bus *bus, int addr, int 
devnum, int regnum)
{ 

         struct oa_tc6 *tc6 = bus->priv; 

         u32 regval; 

         bool ret; 

         u32 mms; 

 

         if (devnum == MDIO_MMD_PCS) 

                 mms = OA_TC6_PHY_PCS_MMS2; 

         else if (devnum == MDIO_MMD_PMAPMD) 

                 mms = OA_TC6_PHY_PMA_PMD_MMS3; 

         else if (devnum == MDIO_MMD_VEND2) 

                 mms = OA_TC6_PHY_VS_PLCA_MMS4; 

         else if (devnum == MDIO_MMD_AN) 

                 mms = OA_TC6_PHY_AUTO_NEG_MMS5; 

         else if (devnum == MDIO_MMD_POWER_UNIT) 

                 mms = OA_TC6_PHY_POWER_UNIT_MMS6; 

         else 

                 return -ENOTSUPP; 

 

         ret = oa_tc6_read_register(tc6, (mms << 16) | regnum, &regval); 

         if (ret) 

                 return -ENODEV; 

 

         return regval; 

} 


static int oa_tc6_mdiobus_write_c45(struct mii_bus *bus, int addr, int 
devnum, int regnum, u16 val)
{ 

         struct oa_tc6 *tc6 = bus->priv; 

         u32 mms; 

 

         if (devnum == MDIO_MMD_PCS) 

                 mms = OA_TC6_PHY_PCS_MMS2; 

         else if (devnum == MDIO_MMD_PMAPMD) 

                 mms = OA_TC6_PHY_PMA_PMD_MMS3; 

         else if (devnum == MDIO_MMD_VEND2) 

                 mms = OA_TC6_PHY_VS_PLCA_MMS4; 

         else if (devnum == MDIO_MMD_AN) 

                 mms = OA_TC6_PHY_AUTO_NEG_MMS5; 

         else if (devnum == MDIO_MMD_POWER_UNIT) 

                 mms = OA_TC6_PHY_POWER_UNIT_MMS6; 

         else 

                 return -ENOTSUPP; 

 

         return oa_tc6_write_register(tc6, (mms << 16) | regnum, val); 

} 


--- Code ends here ---

Best regards,
Parthiban V
> 
>          Andrew
>
Andrew Lunn March 8, 2024, 1:33 p.m. UTC | #5
> Ok, as per the table 6 in the spec, PHY C45 registers are mapped in the 
> MMS like below,
> 
> PHY – PCS Registers (MMD 3)  --->  MMS 2
> PHY – PMA/PMD Registers (MMD 1)  --->   MMS 3
> PHY – Vendor Specific and PLCA Registers (MMD 31)  --->  MMS 4
> PHY – Auto-Negotiation Registers (MMD 7)  --->  MMS 5
> PHY – Power Unit (MMD 13)  --->  MMS 6
> 
> MMD 13 for PHY - Power Unit is not defined in the mdio.h. So in the 
> below code I have defined it locally (MDIO_MMD_POWER_UNIT). May be 
> needed to do this in the mdio.h file when coming to this patch.

Yes, please add it to mdio.h

> /* PHY – Clause 45 registers memory map selector (MMS) as per table 6 in 
> the OPEN Alliance specification.
>   */
> #define OA_TC6_PHY_PCS_MMS2                     2       /* MMD 3 */
> #define OA_TC6_PHY_PMA_PMD_MMS3                 3       /* MMD 1 */
> #define OA_TC6_PHY_VS_PLCA_MMS4                 4       /* MMD 31 */
> #define OA_TC6_PHY_AUTO_NEG_MMS5                5       /* MMD 7 */
> #define OA_TC6_PHY_POWER_UNIT_MMS6              6       /* MMD 13 */
> 
> /* MDIO Manageable Device (MMD) for PHY Power Unit */
> #define MDIO_MMD_POWER_UNIT                     13      /* PHY Power Unit */
> 
> static int oa_tc6_mdiobus_read_c45(struct mii_bus *bus, int addr, int 
> devnum, int regnum)
> { 
> 
>          struct oa_tc6 *tc6 = bus->priv; 
> 
>          u32 regval; 
> 
>          bool ret; 
> 
>          u32 mms; 
> 
>  
> 
>          if (devnum == MDIO_MMD_PCS) 
> 
>                  mms = OA_TC6_PHY_PCS_MMS2; 
> 
>          else if (devnum == MDIO_MMD_PMAPMD) 
> 
>                  mms = OA_TC6_PHY_PMA_PMD_MMS3; 
> 
>          else if (devnum == MDIO_MMD_VEND2) 
> 
>                  mms = OA_TC6_PHY_VS_PLCA_MMS4; 
> 
>          else if (devnum == MDIO_MMD_AN) 
> 
>                  mms = OA_TC6_PHY_AUTO_NEG_MMS5; 
> 
>          else if (devnum == MDIO_MMD_POWER_UNIT) 
> 
>                  mms = OA_TC6_PHY_POWER_UNIT_MMS6; 

I would probably use a switch statement.

> 
>          else 
> 
>                  return -ENOTSUPP; 

802.3 says:

  If a device supports the MDIO interface it shall respond to all
  possible register addresses for the device and return a value of
  zero for undefined and unsupported registers. Writes to undefined
  registers and read-only registers shall have no effect. The
  operation of an MMD shall not be affected by writes to reserved and
  unsupported register bits, and such register bits shall return a
  value of zero when read.

So maybe return 0. ENOTSUPP is wrong, that is an NFS only error
code. The generic one is EOPNOTSUPP. I would say -EOPNOTSUPP is also
O.K.

>          ret = oa_tc6_read_register(tc6, (mms << 16) | regnum, &regval); 
> 
>          if (ret) 
> 
>                  return -ENODEV; 

oa_tc6_read_register() should return an error code, so return whatever
is returns. Don't overwrite error codes. It makes it harder to track
errors through the call stack.

       Andrew
Parthiban Veerasooran March 18, 2024, 11:01 a.m. UTC | #6
Hi Andrew,

On 08/03/24 7:03 pm, Andrew Lunn wrote:
> EXTERNAL EMAIL: Do not click links or open attachments unless you know the content is safe
> 
>> Ok, as per the table 6 in the spec, PHY C45 registers are mapped in the
>> MMS like below,
>>
>> PHY – PCS Registers (MMD 3)  --->  MMS 2
>> PHY – PMA/PMD Registers (MMD 1)  --->   MMS 3
>> PHY – Vendor Specific and PLCA Registers (MMD 31)  --->  MMS 4
>> PHY – Auto-Negotiation Registers (MMD 7)  --->  MMS 5
>> PHY – Power Unit (MMD 13)  --->  MMS 6
>>
>> MMD 13 for PHY - Power Unit is not defined in the mdio.h. So in the
>> below code I have defined it locally (MDIO_MMD_POWER_UNIT). May be
>> needed to do this in the mdio.h file when coming to this patch.
> 
> Yes, please add it to mdio.h
Sure will add it in the mdio.h file.
> 
>> /* PHY – Clause 45 registers memory map selector (MMS) as per table 6 in
>> the OPEN Alliance specification.
>>    */
>> #define OA_TC6_PHY_PCS_MMS2                     2       /* MMD 3 */
>> #define OA_TC6_PHY_PMA_PMD_MMS3                 3       /* MMD 1 */
>> #define OA_TC6_PHY_VS_PLCA_MMS4                 4       /* MMD 31 */
>> #define OA_TC6_PHY_AUTO_NEG_MMS5                5       /* MMD 7 */
>> #define OA_TC6_PHY_POWER_UNIT_MMS6              6       /* MMD 13 */
>>
>> /* MDIO Manageable Device (MMD) for PHY Power Unit */
>> #define MDIO_MMD_POWER_UNIT                     13      /* PHY Power Unit */
>>
>> static int oa_tc6_mdiobus_read_c45(struct mii_bus *bus, int addr, int
>> devnum, int regnum)
>> {
>>
>>           struct oa_tc6 *tc6 = bus->priv;
>>
>>           u32 regval;
>>
>>           bool ret;
>>
>>           u32 mms;
>>
>>
>>
>>           if (devnum == MDIO_MMD_PCS)
>>
>>                   mms = OA_TC6_PHY_PCS_MMS2;
>>
>>           else if (devnum == MDIO_MMD_PMAPMD)
>>
>>                   mms = OA_TC6_PHY_PMA_PMD_MMS3;
>>
>>           else if (devnum == MDIO_MMD_VEND2)
>>
>>                   mms = OA_TC6_PHY_VS_PLCA_MMS4;
>>
>>           else if (devnum == MDIO_MMD_AN)
>>
>>                   mms = OA_TC6_PHY_AUTO_NEG_MMS5;
>>
>>           else if (devnum == MDIO_MMD_POWER_UNIT)
>>
>>                   mms = OA_TC6_PHY_POWER_UNIT_MMS6;
> 
> I would probably use a switch statement.
Ah ok, I will use switch here.
> 
>>
>>           else
>>
>>                   return -ENOTSUPP;
> 
> 802.3 says:
> 
>    If a device supports the MDIO interface it shall respond to all
>    possible register addresses for the device and return a value of
>    zero for undefined and unsupported registers. Writes to undefined
>    registers and read-only registers shall have no effect. The
>    operation of an MMD shall not be affected by writes to reserved and
>    unsupported register bits, and such register bits shall return a
>    value of zero when read.
> 
> So maybe return 0. ENOTSUPP is wrong, that is an NFS only error
> code. The generic one is EOPNOTSUPP. I would say -EOPNOTSUPP is also
> O.K.
Sure, I will use -EOPNOTSUPP in the next version instead of -ENOTSUPP.
> 
>>           ret = oa_tc6_read_register(tc6, (mms << 16) | regnum, &regval);
>>
>>           if (ret)
>>
>>                   return -ENODEV;
> 
> oa_tc6_read_register() should return an error code, so return whatever
> is returns. Don't overwrite error codes. It makes it harder to track
> errors through the call stack.
Ah ok, will return the error code from oa_tc6_read_register() in the 
next version.

Best regards,
Parthiban V
> 
>         Andrew
>
Selvamani Rajagopal March 21, 2024, 6:49 p.m. UTC | #7
> +static int oa_tc6_mdiobus_register(struct oa_tc6 *tc6) {
> +	int ret;
> +
> +	tc6->mdiobus = mdiobus_alloc();
> +	if (!tc6->mdiobus) {
> +		netdev_err(tc6->netdev, "MDIO bus alloc failed\n");
> +		return -ENODEV;
> +	}

Shouldn't it be appropriate to return -ENOMEM here?

> +
> +	tc6->mdiobus->priv = tc6;
> +	tc6->mdiobus->read = oa_tc6_mdiobus_direct_read;
> +	tc6->mdiobus->write = oa_tc6_mdiobus_direct_write;
> +	tc6->mdiobus->name = "oa-tc6-mdiobus";
> +	tc6->mdiobus->parent = tc6->dev;
> +
Parthiban Veerasooran March 22, 2024, 5:50 a.m. UTC | #8
On 22/03/24 12:19 am, Selvamani Rajagopal wrote:
> [Some people who received this message don't often get email from selvamani.rajagopal@onsemi.com. Learn why this is important at https://aka.ms/LearnAboutSenderIdentification ]
> 
> EXTERNAL EMAIL: Do not click links or open attachments unless you know the content is safe
> 
>> +static int oa_tc6_mdiobus_register(struct oa_tc6 *tc6) {
>> +     int ret;
>> +
>> +     tc6->mdiobus = mdiobus_alloc();
>> +     if (!tc6->mdiobus) {
>> +             netdev_err(tc6->netdev, "MDIO bus alloc failed\n");
>> +             return -ENODEV;
>> +     }
> 
> Shouldn't it be appropriate to return -ENOMEM here?
Yes, will change it in the next version.

Best regards,
Parthiban V
> 
>> +
>> +     tc6->mdiobus->priv = tc6;
>> +     tc6->mdiobus->read = oa_tc6_mdiobus_direct_read;
>> +     tc6->mdiobus->write = oa_tc6_mdiobus_direct_write;
>> +     tc6->mdiobus->name = "oa-tc6-mdiobus";
>> +     tc6->mdiobus->parent = tc6->dev;
>> +
>
Parthiban Veerasooran April 12, 2024, 10:43 a.m. UTC | #9
Hi Andrew,

On 18/03/24 4:31 pm, Parthiban.Veerasooran@microchip.com wrote:
> Hi Andrew,
> 
> On 08/03/24 7:03 pm, Andrew Lunn wrote:
>> EXTERNAL EMAIL: Do not click links or open attachments unless you know the content is safe
>>
>>> Ok, as per the table 6 in the spec, PHY C45 registers are mapped in the
>>> MMS like below,
>>>
>>> PHY – PCS Registers (MMD 3)  --->  MMS 2
>>> PHY – PMA/PMD Registers (MMD 1)  --->   MMS 3
>>> PHY – Vendor Specific and PLCA Registers (MMD 31)  --->  MMS 4
>>> PHY – Auto-Negotiation Registers (MMD 7)  --->  MMS 5
>>> PHY – Power Unit (MMD 13)  --->  MMS 6
>>>
>>> MMD 13 for PHY - Power Unit is not defined in the mdio.h. So in the
>>> below code I have defined it locally (MDIO_MMD_POWER_UNIT). May be
>>> needed to do this in the mdio.h file when coming to this patch.
>>
>> Yes, please add it to mdio.h
> Sure will add it in the mdio.h file.
>>
>>> /* PHY – Clause 45 registers memory map selector (MMS) as per table 6 in
>>> the OPEN Alliance specification.
>>>     */
>>> #define OA_TC6_PHY_PCS_MMS2                     2       /* MMD 3 */
>>> #define OA_TC6_PHY_PMA_PMD_MMS3                 3       /* MMD 1 */
>>> #define OA_TC6_PHY_VS_PLCA_MMS4                 4       /* MMD 31 */
>>> #define OA_TC6_PHY_AUTO_NEG_MMS5                5       /* MMD 7 */
>>> #define OA_TC6_PHY_POWER_UNIT_MMS6              6       /* MMD 13 */
>>>
>>> /* MDIO Manageable Device (MMD) for PHY Power Unit */
>>> #define MDIO_MMD_POWER_UNIT                     13      /* PHY Power Unit */
>>>
>>> static int oa_tc6_mdiobus_read_c45(struct mii_bus *bus, int addr, int
>>> devnum, int regnum)
>>> {
>>>
>>>            struct oa_tc6 *tc6 = bus->priv;
>>>
>>>            u32 regval;
>>>
>>>            bool ret;
>>>
>>>            u32 mms;
>>>
>>>
>>>
>>>            if (devnum == MDIO_MMD_PCS)
>>>
>>>                    mms = OA_TC6_PHY_PCS_MMS2;
>>>
>>>            else if (devnum == MDIO_MMD_PMAPMD)
>>>
>>>                    mms = OA_TC6_PHY_PMA_PMD_MMS3;
>>>
>>>            else if (devnum == MDIO_MMD_VEND2)
>>>
>>>                    mms = OA_TC6_PHY_VS_PLCA_MMS4;
>>>
>>>            else if (devnum == MDIO_MMD_AN)
>>>
>>>                    mms = OA_TC6_PHY_AUTO_NEG_MMS5;
>>>
>>>            else if (devnum == MDIO_MMD_POWER_UNIT)
>>>
>>>                    mms = OA_TC6_PHY_POWER_UNIT_MMS6;
>>
>> I would probably use a switch statement.
> Ah ok, I will use switch here.
>>
>>>
>>>            else
>>>
>>>                    return -ENOTSUPP;
>>
>> 802.3 says:
>>
>>     If a device supports the MDIO interface it shall respond to all
>>     possible register addresses for the device and return a value of
>>     zero for undefined and unsupported registers. Writes to undefined
>>     registers and read-only registers shall have no effect. The
>>     operation of an MMD shall not be affected by writes to reserved and
>>     unsupported register bits, and such register bits shall return a
>>     value of zero when read.
>>
>> So maybe return 0. ENOTSUPP is wrong, that is an NFS only error
>> code. The generic one is EOPNOTSUPP. I would say -EOPNOTSUPP is also
>> O.K.
> Sure, I will use -EOPNOTSUPP in the next version instead of -ENOTSUPP.
>>
>>>            ret = oa_tc6_read_register(tc6, (mms << 16) | regnum, &regval);
>>>
>>>            if (ret)
>>>
>>>                    return -ENODEV;
>>
>> oa_tc6_read_register() should return an error code, so return whatever
>> is returns. Don't overwrite error codes. It makes it harder to track
>> errors through the call stack.
> Ah ok, will return the error code from oa_tc6_read_register() in the
> next version.
After implementing the new APIs oa_tc6_mdiobus_read_c45() and 
oa_tc6_mdiobus_write_c45(), I tried testing. But, for some reason these 
APIs are never get called by phy_read_mmd() or phy_write_mmd() as those 
APIs are checking for phydev->is_c45 flag for calling this new c45 APIs 
which is not set in this case.

If the phydev is found via c22, phylib does not set phydev->is_c45, and 
everything ends up going indirect.

To verify the APIs, I just called them locally withing the oa_tc6_init() 
function, there I got the expected results but accessing them through 
phylib is not working because of the above reason.

Do you have any idea or suggestion to test this APIs or do I miss 
anything here?

Best regards,
Parthiban V
> 
> Best regards,
> Parthiban V
>>
>>          Andrew
>>
>
Andrew Lunn April 15, 2024, 1:15 p.m. UTC | #10
On Fri, Apr 12, 2024 at 10:43:15AM +0000, Parthiban.Veerasooran@microchip.com wrote:
> Hi Andrew,
> 
> After implementing the new APIs oa_tc6_mdiobus_read_c45() and 
> oa_tc6_mdiobus_write_c45(), I tried testing. But, for some reason these 
> APIs are never get called by phy_read_mmd() or phy_write_mmd() as those 
> APIs are checking for phydev->is_c45 flag for calling this new c45 APIs 
> which is not set in this case.
> 
> If the phydev is found via c22, phylib does not set phydev->is_c45, and 
> everything ends up going indirect.

We don't have a clean separation between C22/C45 register space and
C22/C45 MDIO bus protocols. As you said, if the PHY is discovered via
C22 bus protocol it assumes it uses C22 protocol.

Those PHYs which do support C45, and in particular, features which
need C45, all call genphy_c45_pma_read_abilities() in there
.get_features function to add in C45 features. However, it will
continue using C45 over C22 because genphy_c45_pma_read_abilities()
only really says the device has C45 registers, not C45 protocol.

I can see two different things you can try.

1) set .read_mmd and .write_mmd in the PHY driver to phy_read_mmd()
   and phy_write_mmd(). That is not great however, since each vendor
   is likely to have their own PHY driver.

2) Add a helper to set is_c45. This however has side effects you might
   not want. e.g. auto-neg will be performed via C45, not C22. This
   might not matter for a T1 PHY where is think auto-neg is not
   actually used. But i don't see anything in TC6 which limits it to
   T1, i could well imagine a T2 or T4 PHY being used with full
   auto-neg.

We really do need to separate C45 registers from C45 protocol in the
phylib core to cleanly solve this.

       Andrew
Parthiban Veerasooran April 16, 2024, 11:02 a.m. UTC | #11
Hi Andrew,

On 15/04/24 6:45 pm, Andrew Lunn wrote:
> EXTERNAL EMAIL: Do not click links or open attachments unless you know the content is safe
> 
> On Fri, Apr 12, 2024 at 10:43:15AM +0000, Parthiban.Veerasooran@microchip.com wrote:
>> Hi Andrew,
>>
>> After implementing the new APIs oa_tc6_mdiobus_read_c45() and
>> oa_tc6_mdiobus_write_c45(), I tried testing. But, for some reason these
>> APIs are never get called by phy_read_mmd() or phy_write_mmd() as those
>> APIs are checking for phydev->is_c45 flag for calling this new c45 APIs
>> which is not set in this case.
>>
>> If the phydev is found via c22, phylib does not set phydev->is_c45, and
>> everything ends up going indirect.
> 
> We don't have a clean separation between C22/C45 register space and
> C22/C45 MDIO bus protocols. As you said, if the PHY is discovered via
> C22 bus protocol it assumes it uses C22 protocol.
> 
Thanks for confirming my understanding.
> Those PHYs which do support C45, and in particular, features which
> need C45, all call genphy_c45_pma_read_abilities() in there
> .get_features function to add in C45 features. However, it will
> continue using C45 over C22 because genphy_c45_pma_read_abilities()
> only really says the device has C45 registers, not C45 protocol.
> 
OK.
> I can see two different things you can try.
> 
> 1) set .read_mmd and .write_mmd in the PHY driver to phy_read_mmd()
>     and phy_write_mmd(). That is not great however, since each vendor
>     is likely to have their own PHY driver.
> 
I tried this approach and it works as expected. Means whenever there is 
a c45 register access, it directly uses the 
oa_tc6_read_c45()/oa_tc6_write_c45() functions. Herewith I have attached 
the patch 
(v4-0006-net-ethernet-oa_tc6-implement-internal-PHY-initia.patch) which 
has this new implementation for your reference. Is this you expected? 
Can you comment on this?
> 2) Add a helper to set is_c45. This however has side effects you might
>     not want. e.g. auto-neg will be performed via C45, not C22. This
>     might not matter for a T1 PHY where is think auto-neg is not
>     actually used. But i don't see anything in TC6 which limits it to
>     T1, i could well imagine a T2 or T4 PHY being used with full
>     auto-neg.
> 
I tried this approach by setting up is_c45 flag when I use 
phy_read_mmd() function but ended up with the kernel call trace 
(c45_kernel_call_trace.png) attached here for your reference.
> We really do need to separate C45 registers from C45 protocol in the
> phylib core to cleanly solve this.
OK. In my opinion, until having the above implementation in the phylib 
core, shall we go with the first approach [1] I tried out as it is 
fulfilling our requirement? What do you think?

Best regards,
Parthiban V
> 
>         Andrew
>
Andrew Lunn April 16, 2024, 6:18 p.m. UTC | #12
> I tried this approach and it works as expected. Means whenever there is 
> a c45 register access, it directly uses the 
> oa_tc6_read_c45()/oa_tc6_write_c45() functions. Herewith I have attached 
> the patch 
> (v4-0006-net-ethernet-oa_tc6-implement-internal-PHY-initia.patch) which 
> has this new implementation for your reference. Is this you expected? 
> Can you comment on this?

Please just post a new patch series. I will then review it just like
other patches. Its O.K. to send patch series frequently, not just more
than one per day.

> I tried this approach by setting up is_c45 flag when I use 
> phy_read_mmd() function but ended up with the kernel call trace 
> (c45_kernel_call_trace.png) attached here for your reference.

Please post plain ASCII. I assume you have a serial port, so you
should be able to capture it. I'm not too surprised though, no other
driver plays with is_c45.

       Andrew
Parthiban Veerasooran April 17, 2024, 8:55 a.m. UTC | #13
Hi Andrew,

On 16/04/24 11:48 pm, Andrew Lunn wrote:
> EXTERNAL EMAIL: Do not click links or open attachments unless you know the content is safe
> 
>> I tried this approach and it works as expected. Means whenever there is
>> a c45 register access, it directly uses the
>> oa_tc6_read_c45()/oa_tc6_write_c45() functions. Herewith I have attached
>> the patch
>> (v4-0006-net-ethernet-oa_tc6-implement-internal-PHY-initia.patch) which
>> has this new implementation for your reference. Is this you expected?
>> Can you comment on this?
> 
> Please just post a new patch series. I will then review it just like
> other patches. Its O.K. to send patch series frequently, not just more
> than one per day.
> 
Sure, then I will send v4 patch series with this changes as well soon 
for the review.
>> I tried this approach by setting up is_c45 flag when I use
>> phy_read_mmd() function but ended up with the kernel call trace
>> (c45_kernel_call_trace.png) attached here for your reference.
> 
> Please post plain ASCII. I assume you have a serial port, so you
> should be able to capture it. I'm not too surprised though, no other
> driver plays with is_c45.
O.K. Please find the below kernel trace,

[15890.127525] ------------[ cut here ]------------
[15890.127540] phy_start_aneg+0x0/0x58: returned: -22
[15890.127592] WARNING: CPU: 0 PID: 3937 at drivers/net/phy/phy.c:1233 
phy_state_machine+0xac/0x2f0
[15890.127602] Modules linked in: lan865x_t1s(O) microchip_t1s(O) rfcomm 
snd_seq_dummy snd_hrtimer snd_seq snd_seq_device cmac algif_hash 
aes_arm64 aes_generic algif_skcipher af_alg bnep brcmfmac_wcc hci_uart 
btbcm bluetooth brcmfmac binfmt_misc bcm2835_v4l2(C) rpivid_hevc(C) 
bcm2835_codec(C) bcm2835_isp(C) v4l2_mem2mem brcmutil 
bcm2835_mmal_vchiq(C) videobuf2_vmalloc videobuf2_dma_contig cfg80211 
ecdh_generic ecc videobuf2_memops videobuf2_v4l2 rfkill videodev libaes 
raspberrypi_hwmon videobuf2_common snd_bcm2835(C) raspberrypi_gpiomem mc 
vc_sm_cma(C) nvmem_rmem gpio_fan uio_pdrv_genirq uio i2c_dev fuse dm_mod 
ip_tables x_tables ipv6 spidev vc4 snd_soc_hdmi_codec drm_display_helper 
cec drm_dma_helper v3d gpu_sched drm_kms_helper drm_shmem_helper drm 
drm_panel_orientation_quirks i2c_brcmstb snd_soc_core spi_bcm2835 
snd_compress snd_pcm_dmaengine snd_pcm snd_timer snd backlight [last 
unloaded: microchip_t1s(O)]
[15890.127756] CPU: 0 PID: 3937 Comm: kworker/0:1 Tainted: G        WC O 
       6.6.20+rpt-rpi-v8 #1  Debian 1:6.6.20-1+rpt1
[15890.127763] Hardware name: Raspberry Pi 4 Model B Rev 1.4 (DT)
[15890.127767] Workqueue: events_power_efficient phy_state_machine
[15890.127773] pstate: 60000005 (nZCv daif -PAN -UAO -TCO -DIT -SSBS 
BTYPE=--)
[15890.127778] pc : phy_state_machine+0xac/0x2f0
[15890.127783] lr : phy_state_machine+0xac/0x2f0
[15890.127787] sp : ffffffc086c2bd60
[15890.127790] x29: ffffffc086c2bd60 x28: 0000000000000000 x27: 
0000000000000000
[15890.127798] x26: ffffff81fef781a8 x25: ffffff8101906640 x24: 
ffffff810010ca05
[15890.127806] x23: 00000000ffffffea x22: ffffff8103e41c98 x21: 
0000000000000004
[15890.127815] x20: ffffff8103e41cf0 x19: ffffff8103e41800 x18: 
00000000fffffffe
[15890.127822] x17: 0000000000000000 x16: ffffffe7346b5b60 x15: 
ffffffc086c2b960
[15890.127830] x14: 0000000000000000 x13: 32322d203a64656e x12: 
7275746572203a38
[15890.127837] x11: 3578302f3078302b x10: ffffffe735ca3708 x9 : 
ffffffe73470946c
[15890.127845] x8 : 00000000ffffefff x7 : ffffffe735ca3708 x6 : 
80000000fffff000
[15890.127852] x5 : ffffff81fef67d48 x4 : 0000000000000000 x3 : 
0000000000000027
[15890.127860] x2 : 0000000000000000 x1 : 0000000000000000 x0 : 
ffffff8105f1bd80
[15890.127868] Call trace:
[15890.127872]  phy_state_machine+0xac/0x2f0
[15890.127878]  process_one_work+0x148/0x3b8
[15890.127887]  worker_thread+0x32c/0x450
[15890.127893]  kthread+0x11c/0x128
[15890.127902]  ret_from_fork+0x10/0x20
[15890.127908] ---[ end trace 0000000000000000 ]---

Best regards,
Parthiban V
> 
>         Andrew
>
diff mbox series

Patch

diff --git a/drivers/net/ethernet/oa_tc6.c b/drivers/net/ethernet/oa_tc6.c
index f8593b793291..82b4de13438f 100644
--- a/drivers/net/ethernet/oa_tc6.c
+++ b/drivers/net/ethernet/oa_tc6.c
@@ -7,9 +7,15 @@ 
 
 #include <linux/bitfield.h>
 #include <linux/iopoll.h>
+#include <linux/mdio.h>
+#include <linux/phy.h>
 #include <linux/oa_tc6.h>
 
 /* OPEN Alliance TC6 registers */
+/* Standard Capabilities Register */
+#define OA_TC6_REG_STDCAP			0x0002
+#define STDCAP_DIRECT_PHY_REG_ACCESS		BIT(8)
+
 /* Reset Control and Status Register */
 #define OA_TC6_REG_RESET			0x0003
 #define RESET_SWRESET				BIT(0)	/* Software Reset */
@@ -25,6 +31,10 @@ 
 #define INT_MASK0_RX_BUFFER_OVERFLOW_ERR_MASK	BIT(3)
 #define INT_MASK0_TX_PROTOCOL_ERR_MASK		BIT(0)
 
+/* PHY Clause 22 and 29 registers base address and mask */
+#define OA_TC6_PHY_STD_REG_ADDR_BASE		0xFF00
+#define OA_TC6_PHY_STD_REG_ADDR_MASK		0x3F
+
 /* Control command header */
 #define OA_TC6_CTRL_HEADER_DATA_NOT_CTRL	BIT(31)
 #define OA_TC6_CTRL_HEADER_WRITE		BIT(29)
@@ -46,6 +56,10 @@ 
 
 /* Internal structure for MAC-PHY drivers */
 struct oa_tc6 {
+	struct device *dev;
+	struct net_device *netdev;
+	struct phy_device *phydev;
+	struct mii_bus *mdiobus;
 	struct spi_device *spi;
 	struct mutex spi_ctrl_lock; /* Protects spi control transfer */
 	void *spi_ctrl_tx_buf;
@@ -298,6 +312,130 @@  int oa_tc6_write_register(struct oa_tc6 *tc6, u32 address, u32 value)
 }
 EXPORT_SYMBOL_GPL(oa_tc6_write_register);
 
+static int oa_tc6_check_phy_reg_direct_access_capability(struct oa_tc6 *tc6)
+{
+	u32 regval;
+	int ret;
+
+	ret = oa_tc6_read_register(tc6, OA_TC6_REG_STDCAP, &regval);
+	if (ret)
+		return ret;
+
+	if (!(regval & STDCAP_DIRECT_PHY_REG_ACCESS))
+		return -ENODEV;
+
+	return 0;
+}
+
+static void oa_tc6_handle_link_change(struct net_device *netdev)
+{
+	phy_print_status(netdev->phydev);
+}
+
+static int oa_tc6_mdiobus_direct_read(struct mii_bus *bus, int addr, int regnum)
+{
+	struct oa_tc6 *tc6 = bus->priv;
+	u32 regval;
+	bool ret;
+
+	ret = oa_tc6_read_register(tc6, OA_TC6_PHY_STD_REG_ADDR_BASE |
+				   (regnum & OA_TC6_PHY_STD_REG_ADDR_MASK),
+				   &regval);
+	if (ret)
+		return -ENODEV;
+
+	return regval;
+}
+
+static int oa_tc6_mdiobus_direct_write(struct mii_bus *bus, int addr, int regnum,
+				       u16 val)
+{
+	struct oa_tc6 *tc6 = bus->priv;
+
+	return oa_tc6_write_register(tc6, OA_TC6_PHY_STD_REG_ADDR_BASE |
+				     (regnum & OA_TC6_PHY_STD_REG_ADDR_MASK),
+				     val);
+}
+
+static int oa_tc6_mdiobus_register(struct oa_tc6 *tc6)
+{
+	int ret;
+
+	tc6->mdiobus = mdiobus_alloc();
+	if (!tc6->mdiobus) {
+		netdev_err(tc6->netdev, "MDIO bus alloc failed\n");
+		return -ENODEV;
+	}
+
+	tc6->mdiobus->priv = tc6;
+	tc6->mdiobus->read = oa_tc6_mdiobus_direct_read;
+	tc6->mdiobus->write = oa_tc6_mdiobus_direct_write;
+	tc6->mdiobus->name = "oa-tc6-mdiobus";
+	tc6->mdiobus->parent = tc6->dev;
+
+	snprintf(tc6->mdiobus->id, ARRAY_SIZE(tc6->mdiobus->id), "%s",
+		 dev_name(&tc6->spi->dev));
+
+	ret = mdiobus_register(tc6->mdiobus);
+	if (ret) {
+		netdev_err(tc6->netdev, "Could not register MDIO bus\n");
+		mdiobus_free(tc6->mdiobus);
+		return ret;
+	}
+
+	return 0;
+}
+
+static void oa_tc6_mdiobus_unregister(struct oa_tc6 *tc6)
+{
+	mdiobus_unregister(tc6->mdiobus);
+	mdiobus_free(tc6->mdiobus);
+}
+
+static int oa_tc6_phy_init(struct oa_tc6 *tc6)
+{
+	int ret;
+
+	ret = oa_tc6_check_phy_reg_direct_access_capability(tc6);
+	if (ret) {
+		netdev_err(tc6->netdev,
+			   "Direct PHY register access is not supported by the MAC-PHY\n");
+		return ret;
+	}
+
+	ret = oa_tc6_mdiobus_register(tc6);
+	if (ret)
+		return ret;
+
+	tc6->phydev = phy_find_first(tc6->mdiobus);
+	if (!tc6->phydev) {
+		netdev_err(tc6->netdev, "No PHY found\n");
+		oa_tc6_mdiobus_unregister(tc6);
+		return -ENODEV;
+	}
+
+	tc6->phydev->is_internal = true;
+	ret = phy_connect_direct(tc6->netdev, tc6->phydev,
+				 &oa_tc6_handle_link_change,
+				 PHY_INTERFACE_MODE_INTERNAL);
+	if (ret) {
+		netdev_err(tc6->netdev, "Can't attach PHY to %s\n",
+			   tc6->mdiobus->id);
+		oa_tc6_mdiobus_unregister(tc6);
+		return ret;
+	}
+
+	phy_attached_info(tc6->netdev->phydev);
+
+	return 0;
+}
+
+static void oa_tc6_phy_exit(struct oa_tc6 *tc6)
+{
+	phy_disconnect(tc6->phydev);
+	oa_tc6_mdiobus_unregister(tc6);
+}
+
 static int oa_tc6_read_sw_reset_status(struct oa_tc6 *tc6)
 {
 	u32 regval;
@@ -351,11 +489,12 @@  static int oa_tc6_unmask_macphy_error_interrupts(struct oa_tc6 *tc6)
 /**
  * oa_tc6_init - allocates and initializes oa_tc6 structure.
  * @spi: device with which data will be exchanged.
+ * @netdev: network device interface structure.
  *
  * Returns pointer reference to the oa_tc6 structure if the MAC-PHY
  * initialization is successful otherwise NULL.
  */
-struct oa_tc6 *oa_tc6_init(struct spi_device *spi)
+struct oa_tc6 *oa_tc6_init(struct spi_device *spi, struct net_device *netdev)
 {
 	struct oa_tc6 *tc6;
 	int ret;
@@ -365,6 +504,8 @@  struct oa_tc6 *oa_tc6_init(struct spi_device *spi)
 		return NULL;
 
 	tc6->spi = spi;
+	tc6->netdev = netdev;
+	SET_NETDEV_DEV(netdev, &spi->dev);
 	mutex_init(&tc6->spi_ctrl_lock);
 
 	/* Set the SPI controller to pump at realtime priority */
@@ -395,10 +536,27 @@  struct oa_tc6 *oa_tc6_init(struct spi_device *spi)
 		return NULL;
 	}
 
+	ret = oa_tc6_phy_init(tc6);
+	if (ret) {
+		dev_err(&tc6->spi->dev,
+			"MAC internal PHY initialization failed: %d\n", ret);
+		return NULL;
+	}
+
 	return tc6;
 }
 EXPORT_SYMBOL_GPL(oa_tc6_init);
 
+/**
+ * oa_tc6_exit - exit function.
+ * @tc6: oa_tc6 struct.
+ */
+void oa_tc6_exit(struct oa_tc6 *tc6)
+{
+	oa_tc6_phy_exit(tc6);
+}
+EXPORT_SYMBOL_GPL(oa_tc6_exit);
+
 MODULE_DESCRIPTION("OPEN Alliance 10BASE‑T1x MAC‑PHY Serial Interface Lib");
 MODULE_AUTHOR("Parthiban Veerasooran <parthiban.veerasooran@microchip.com>");
 MODULE_LICENSE("GPL");
diff --git a/include/linux/oa_tc6.h b/include/linux/oa_tc6.h
index 85aeecf87306..606ba9f1e663 100644
--- a/include/linux/oa_tc6.h
+++ b/include/linux/oa_tc6.h
@@ -7,11 +7,13 @@ 
  * Author: Parthiban Veerasooran <parthiban.veerasooran@microchip.com>
  */
 
+#include <linux/etherdevice.h>
 #include <linux/spi/spi.h>
 
 struct oa_tc6;
 
-struct oa_tc6 *oa_tc6_init(struct spi_device *spi);
+struct oa_tc6 *oa_tc6_init(struct spi_device *spi, struct net_device *netdev);
+void oa_tc6_exit(struct oa_tc6 *tc6);
 int oa_tc6_write_register(struct oa_tc6 *tc6, u32 address, u32 value);
 int oa_tc6_write_registers(struct oa_tc6 *tc6, u32 address, u32 value[],
 			   u8 length);