From 03dd82de447db543cccff56251d4a8e8f76309c9 Mon Sep 17 00:00:00 2001 From: CoolStar Date: Mon, 30 Oct 2023 17:53:47 -0700 Subject: [PATCH] Add I2C and GPIO drivers --- README.md | 4 +- build/RockchipDrivers.sln | 16 + drivers/gpio/LICENSE.txt | 13 + drivers/gpio/README.md | 12 + drivers/gpio/rk3xgpio/rk3xgpio.c | 2476 +++++++++++++++++ drivers/gpio/rk3xgpio/rk3xgpio.inx | 77 + drivers/gpio/rk3xgpio/rk3xgpio.rc | 41 + drivers/gpio/rk3xgpio/rk3xgpio.vcxproj | 113 + .../gpio/rk3xgpio/rk3xgpio.vcxproj.Filters | 36 + drivers/i2c/LICENSE.txt | 13 + drivers/i2c/README.md | 11 + drivers/i2c/rk3xi2c/clocks.c | 220 ++ drivers/i2c/rk3xi2c/driver.h | 172 ++ drivers/i2c/rk3xi2c/mdlchain.c | 100 + drivers/i2c/rk3xi2c/rk3xi2c.c | 551 ++++ drivers/i2c/rk3xi2c/rk3xi2c.h | 204 ++ drivers/i2c/rk3xi2c/rk3xi2c.inx | 77 + drivers/i2c/rk3xi2c/rk3xi2c.rc | 41 + drivers/i2c/rk3xi2c/rk3xi2c.vcxproj | 114 + drivers/i2c/rk3xi2c/rk3xi2c.vcxproj.Filters | 56 + drivers/i2c/rk3xi2c/rockchipi2c.c | 443 +++ drivers/i2c/rk3xi2c/trace.h | 43 + 22 files changed, 4831 insertions(+), 2 deletions(-) create mode 100644 drivers/gpio/LICENSE.txt create mode 100644 drivers/gpio/README.md create mode 100644 drivers/gpio/rk3xgpio/rk3xgpio.c create mode 100644 drivers/gpio/rk3xgpio/rk3xgpio.inx create mode 100644 drivers/gpio/rk3xgpio/rk3xgpio.rc create mode 100644 drivers/gpio/rk3xgpio/rk3xgpio.vcxproj create mode 100644 drivers/gpio/rk3xgpio/rk3xgpio.vcxproj.Filters create mode 100644 drivers/i2c/LICENSE.txt create mode 100644 drivers/i2c/README.md create mode 100644 drivers/i2c/rk3xi2c/clocks.c create mode 100644 drivers/i2c/rk3xi2c/driver.h create mode 100644 drivers/i2c/rk3xi2c/mdlchain.c create mode 100644 drivers/i2c/rk3xi2c/rk3xi2c.c create mode 100644 drivers/i2c/rk3xi2c/rk3xi2c.h create mode 100644 drivers/i2c/rk3xi2c/rk3xi2c.inx create mode 100644 drivers/i2c/rk3xi2c/rk3xi2c.rc create mode 100644 drivers/i2c/rk3xi2c/rk3xi2c.vcxproj create mode 100644 drivers/i2c/rk3xi2c/rk3xi2c.vcxproj.Filters create mode 100644 drivers/i2c/rk3xi2c/rockchipi2c.c create mode 100644 drivers/i2c/rk3xi2c/trace.h diff --git a/README.md b/README.md index 8eb12c8..1f9e973 100644 --- a/README.md +++ b/README.md @@ -27,8 +27,8 @@ This repository contains drivers for RK35xx-based platforms, with a focus on RK3 |CSI||🔴 Not working|| |GMAC Ethernet||🔴 Not working|| |UART||🔴 Not working|No OS driver but debugging does work on UART2, being configured by UEFI.| -|GPIO||🔴 Not working|| -|I2C||🔴 Not working|| +|GPIO|[rk3xgpio](https://github.com/worproject/Rockchip-Windows-Drivers/tree/master/drivers/gpio)|🟢 Working|| +|I2C|[rk3xi2c](https://github.com/worproject/Rockchip-Windows-Drivers/tree/master/drivers/i2c)|🟢 Working|| |I2S||🔴 Not working|| |SPI||🔴 Not working|| |CAN bus||🔴 Not working|| diff --git a/build/RockchipDrivers.sln b/build/RockchipDrivers.sln index 6577a9b..6331150 100644 --- a/build/RockchipDrivers.sln +++ b/build/RockchipDrivers.sln @@ -5,6 +5,10 @@ VisualStudioVersion = 17.4.33205.214 MinimumVisualStudioVersion = 10.0.40219.1 Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "dwcsdhc", "..\drivers\sd\dwcsdhc\dwcsdhc.vcxproj", "{10452736-7C4F-4206-94F9-AD634213C9C4}" EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "rk3xi2c", "..\drivers\i2c\rk3xi2c\rk3xi2c.vcxproj", "{B3E71397-9BE4-492B-AAED-4D056E59CB1F}" +EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "rk3xgpio", "..\drivers\gpio\rk3xgpio\rk3xgpio.vcxproj", "{A80FE9DD-C140-40F6-A3F4-55A2A55BFAD4}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|ARM64 = Debug|ARM64 @@ -17,6 +21,18 @@ Global {10452736-7C4F-4206-94F9-AD634213C9C4}.Release|ARM64.ActiveCfg = Release|ARM64 {10452736-7C4F-4206-94F9-AD634213C9C4}.Release|ARM64.Build.0 = Release|ARM64 {10452736-7C4F-4206-94F9-AD634213C9C4}.Release|ARM64.Deploy.0 = Release|ARM64 + {B3E71397-9BE4-492B-AAED-4D056E59CB1F}.Debug|ARM64.ActiveCfg = Debug|ARM64 + {B3E71397-9BE4-492B-AAED-4D056E59CB1F}.Debug|ARM64.Build.0 = Debug|ARM64 + {B3E71397-9BE4-492B-AAED-4D056E59CB1F}.Debug|ARM64.Deploy.0 = Debug|ARM64 + {B3E71397-9BE4-492B-AAED-4D056E59CB1F}.Release|ARM64.ActiveCfg = Release|ARM64 + {B3E71397-9BE4-492B-AAED-4D056E59CB1F}.Release|ARM64.Build.0 = Release|ARM64 + {B3E71397-9BE4-492B-AAED-4D056E59CB1F}.Release|ARM64.Deploy.0 = Release|ARM64 + {A80FE9DD-C140-40F6-A3F4-55A2A55BFAD4}.Debug|ARM64.ActiveCfg = Debug|ARM64 + {A80FE9DD-C140-40F6-A3F4-55A2A55BFAD4}.Debug|ARM64.Build.0 = Debug|ARM64 + {A80FE9DD-C140-40F6-A3F4-55A2A55BFAD4}.Debug|ARM64.Deploy.0 = Debug|ARM64 + {A80FE9DD-C140-40F6-A3F4-55A2A55BFAD4}.Release|ARM64.ActiveCfg = Release|ARM64 + {A80FE9DD-C140-40F6-A3F4-55A2A55BFAD4}.Release|ARM64.Build.0 = Release|ARM64 + {A80FE9DD-C140-40F6-A3F4-55A2A55BFAD4}.Release|ARM64.Deploy.0 = Release|ARM64 EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE diff --git a/drivers/gpio/LICENSE.txt b/drivers/gpio/LICENSE.txt new file mode 100644 index 0000000..3880e06 --- /dev/null +++ b/drivers/gpio/LICENSE.txt @@ -0,0 +1,13 @@ +Copyright 2023 CoolStar + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. \ No newline at end of file diff --git a/drivers/gpio/README.md b/drivers/gpio/README.md new file mode 100644 index 0000000..aa42d40 --- /dev/null +++ b/drivers/gpio/README.md @@ -0,0 +1,12 @@ +Rockchip 3xxx GPIO Driver + +Tested on RK3588S + +Implements Windows GpioClx so most existing GPIO drivers should attach and work + +Tested operations: +* Connect (I/O) +* Read +* Write +* Edge Interrupts +* Level Interrupts diff --git a/drivers/gpio/rk3xgpio/rk3xgpio.c b/drivers/gpio/rk3xgpio/rk3xgpio.c new file mode 100644 index 0000000..f8410b2 --- /dev/null +++ b/drivers/gpio/rk3xgpio/rk3xgpio.c @@ -0,0 +1,2476 @@ +// +// ------------------------------------------------------------------- Includes +// + +#include +#include +#include + +// +// -------------------------------------------------------------------- Defines +// + +#define GPIO_TYPE_V1 (0) /* GPIO Version ID reserved */ +#define GPIO_TYPE_V2 (0x01000C2B) /* GPIO Version ID 0x01000C2B */ +#define GPIO_TYPE_V2_1 (0x0101157C) /* GPIO Version ID 0x0101157C */ + +// +// Define total number of pins on the simulated GPIO controller. +// + +#define SIM_GPIO_PINS_PER_BANK (32) +#define SIM_GPIO_TOTAL_PINS SIM_GPIO_PINS_PER_BANK +#define SIM_GPIO_TOTAL_BANKS (1) //Each bank is a separate ACPI device +#define BANK_SIZE SIM_GPIO_PINS_PER_BANK * sizeof(UINT32) * 2 + +// +// Pool tag for RockchipGpio allocations. +// + +#define SIM_GPIO_POOL_TAG 'PGKR' + +// +// Define that controls whether f-state based power management will be supported +// or not by the client driver. +// + +#define ENABLE_F_STATE_POWER_MGMT + +// +// Define F1 state residency (in sec), latency values (in sec) and nominal +// power. +// +// This sample demonstrates F-state management effected as part of a critical +// system transition (when the system enters connected-standby). To prevent a +// GPIO bank from being transitioned into a low power state as part of runtime +// idle power management (by mini-PEP), specify very high values for residency +// and transition latency. +// +// If GPIO banks need to be runtime power managed, then specify more reasonable +// values below. +// + +#ifdef ENABLE_F_STATE_POWER_MGMT + +#define SIM_GPIO_F1_NOMINAL_POWER (0) +#define SIM_GPIO_F1_RESIDENCY (8*60*60) // 8hrs +#define SIM_GPIO_F1_TRANSITION (1*60*60) // 1hr + +#endif + +// +// Macro for pointer arithmetic. +// + +#define Add2Ptr(Ptr, Value) ((PVOID)((PUCHAR)(Ptr) + (Value))) + +// +// Determine whether the given pin is reserved or not. Currently no pins are +// reserved on the simulated GPIO controller. +// + +__pragma(warning(disable: 4127)) // conditional expression is a constant + +// +// ---------------------------------------------------------------------- Types +// + +// +// Define the registers within the SimGPIO controller. There are 32 pins per +// controller. Note this is a logical device and thus may correspond to a +// physical bank or module if the GPIO controller in hardware has more than +// 32 pins. +// + +/** + * struct rockchip_gpio_regs + * @PortDR: data register + * @PortDDR: data direction register + * @IntEn: interrupt enable + * @IntMask: interrupt mask + * @IntType: interrupt trigger type, such as high, low, edge trriger type. + * @IntPolarity: interrupt polarity enable register + * @IntBothEdge: interrupt bothedge enable register + * @IntStatus: interrupt status register + * @IntRawStatus: IntStatus = IntRawStatus & IntMask + * @Debounce: enable debounce for interrupt signal + * @DBClkDivEn: enable divider for debounce clock + * @DBClkDivCon: setting for divider of debounce clock + * @PortEOI: end of interrupt of the port + * @ExtPort: port data from external + * @VersionID: controller version register + */ +typedef struct _ROCKCHIP_GPIO_REGISTERS { + UINT32 PortDR; + UINT32 PortDDR; + UINT32 IntEn; + UINT32 IntMask; + UINT32 IntType; + UINT32 IntPolarity; + UINT32 IntBothEdge; + UINT32 IntStatus; + UINT32 IntRawStatus; + UINT32 Debounce; + UINT32 DBClkDivEn; + UINT32 DBClkDivCon; + UINT32 PortEOI; + UINT32 ExtPort; + UINT32 VersionID; +} ROCKCHIP_GPIO_REGISTERS, *PROCKCHIP_GPIO_REGISTERS; + +static const ROCKCHIP_GPIO_REGISTERS GPIO_REGS_V1 = { + .PortDR = 0x00, + .PortDDR = 0x04, + .IntEn = 0x30, + .IntMask = 0x34, + .IntType = 0x38, + .IntPolarity = 0x3c, + .IntStatus = 0x40, + .IntRawStatus = 0x44, + .Debounce = 0x48, + .PortEOI = 0x4c, + .ExtPort = 0x50, +}; + +static const ROCKCHIP_GPIO_REGISTERS GPIO_REGS_V2 = { + .PortDR = 0x00, + .PortDDR = 0x08, + .IntEn = 0x10, + .IntMask = 0x18, + .IntType = 0x20, + .IntPolarity = 0x28, + .IntBothEdge = 0x30, + .IntStatus = 0x50, + .IntRawStatus = 0x58, + .Debounce = 0x38, + .DBClkDivEn = 0x40, + .DBClkDivCon = 0x48, + .PortEOI = 0x60, + .ExtPort = 0x70, + .VersionID = 0x78, +}; + +typedef struct _ROCKCHIP_SAVED_CONTEXT { + UINT32 DataRegister; + UINT32 DataDirectionRegister; + UINT32 IntMask; + UINT32 IntType; + UINT32 IntPolarity; +} ROCKCHIP_SAVED_CONTEXT, *PROCKCHIP_SAVED_CONTEXT; + +typedef struct _ROCKCHIP_GPIO_BANK { + UINT8* VirtualBaseAddress; + UINT32 SavedMasks; + UINT32 PinBase; + UINT32 NrPins; + UINT32 GpioType; + ROCKCHIP_GPIO_REGISTERS Registers; + ULONG Length; + ROCKCHIP_SAVED_CONTEXT SavedContext; +} ROCKCHIP_GPIO_BANK, *PROCKCHIP_GPIO_BANK; + +#define MASK(PIN) (1 << PIN) + +// +// The SimGPIO client driver device extension. +// + +typedef struct _SIM_GPIO_CONTEXT { + USHORT TotalPins; + LARGE_INTEGER PhysicalBaseAddress; + UINT8* VirtualBaseAddress; + ULONG Length; + ROCKCHIP_GPIO_BANK Banks[SIM_GPIO_TOTAL_BANKS]; +} SIM_GPIO_CONTEXT, *PSIM_GPIO_CONTEXT; + +UINT32 read32(PROCKCHIP_GPIO_BANK pBank, UINT32 reg) +{ + return *(UINT32*)((CHAR*)pBank->VirtualBaseAddress + reg); +} + +void write32(PROCKCHIP_GPIO_BANK pBank, UINT32 reg, UINT32 val) { + *(UINT32*)((CHAR*)pBank->VirtualBaseAddress + reg) = val; +} + +void gpio_write32_v2(PROCKCHIP_GPIO_BANK pBank, UINT32 reg, UINT32 val) { + write32(pBank, reg, (val & 0xffff) | 0xffff0000); + write32(pBank, reg + 0x4, (val >> 16) | 0xffff0000); +} + +UINT32 gpio_read32_v2(PROCKCHIP_GPIO_BANK pBank, UINT32 reg) +{ + return read32(pBank, reg + 0x4) << 16 | read32(pBank, reg); +} + +void gpio_write32(PROCKCHIP_GPIO_BANK pBank, UINT32 reg, UINT32 val) +{ + if (pBank->GpioType == GPIO_TYPE_V2) + gpio_write32_v2(pBank, reg, val); + else + write32(pBank, reg, val); +} + +UINT32 gpio_read32(PROCKCHIP_GPIO_BANK pBank, UINT32 reg) +{ + UINT32 value; + if (pBank->GpioType == GPIO_TYPE_V2) + value = gpio_read32_v2(pBank, reg); + else + value = read32(pBank, reg); + return value; +} + +// +// ----------------------------------------------------------------- Prototypes +// + +DRIVER_INITIALIZE DriverEntry; + +EVT_WDF_DRIVER_UNLOAD RockchipGpioEvtDriverUnload; +EVT_WDF_DRIVER_DEVICE_ADD RockchipGpioEvtDeviceAdd; + +// +// General interfaces. +// + +GPIO_CLIENT_PREPARE_CONTROLLER RockchipGpioPrepareController; +GPIO_CLIENT_RELEASE_CONTROLLER RockchipGpioReleaseController; +GPIO_CLIENT_QUERY_CONTROLLER_BASIC_INFORMATION + RockchipGpioQueryControllerBasicInformation; + +GPIO_CLIENT_QUERY_SET_CONTROLLER_INFORMATION + RockchipGpioQuerySetControllerInformation; + +GPIO_CLIENT_START_CONTROLLER RockchipGpioStartController; +GPIO_CLIENT_STOP_CONTROLLER RockchipGpioStopController; + +// +// Interrupt enable, disable, mask and unmask handlers. +// + +GPIO_CLIENT_ENABLE_INTERRUPT RockchipGpioEnableInterrupt; +GPIO_CLIENT_DISABLE_INTERRUPT RockchipGpioDisableInterrupt; +GPIO_CLIENT_MASK_INTERRUPTS RockchipGpioMaskInterrupts; +GPIO_CLIENT_UNMASK_INTERRUPT RockchipGpioUnmaskInterrupt; +GPIO_CLIENT_RECONFIGURE_INTERRUPT RockchipGpioReconfigureInterrupt; + +// +// Handlers to query active/enabled interrupts and clear active interrupts. +// + +GPIO_CLIENT_QUERY_ACTIVE_INTERRUPTS RockchipGpioQueryActiveInterrupts; +GPIO_CLIENT_CLEAR_ACTIVE_INTERRUPTS RockchipGpioClearActiveInterrupts; +GPIO_CLIENT_QUERY_ENABLED_INTERRUPTS RockchipGpioQueryEnabledInterrupts; + +// +// Handlers for GPIO I/O operations. +// + +GPIO_CLIENT_CONNECT_IO_PINS RockchipGpioConnectIoPins; +GPIO_CLIENT_DISCONNECT_IO_PINS RockchipGpioDisconnectIoPins; +GPIO_CLIENT_READ_PINS_MASK RockchipGpioReadGpioPins; +GPIO_CLIENT_WRITE_PINS_MASK RockchipGpioWriteGpioPins; + +// +// Handlers for save and restore hardware context callbacks. +// + +GPIO_CLIENT_SAVE_BANK_HARDWARE_CONTEXT RockchipGpioSaveBankHardwareContext; +GPIO_CLIENT_RESTORE_BANK_HARDWARE_CONTEXT RockchipGpioRestoreBankHardwareContext; + +// +// -------------------------------------------------------------------- Pragmas +// + +#pragma alloc_text(INIT, DriverEntry) +#pragma alloc_text(PAGE, RockchipGpioEvtDriverUnload) +#pragma alloc_text(PAGE, RockchipGpioEvtDeviceAdd) + +// +// ------------------------------------------------------------------ Functions +// + +NTSTATUS +DriverEntry ( + _In_ PDRIVER_OBJECT DriverObject, + _In_ PUNICODE_STRING RegistryPath + ) + +/*++ + +Routine Description: + + This routine is the driver initialization entry point. + +Arguments: + + DriverObject - Pointer to the driver object created by the I/O manager. + + RegistryPath - Pointer to the driver specific registry key. + +Return Value: + + NTSTATUS code. + +--*/ + +{ + WDFDRIVER Driver; + WDF_DRIVER_CONFIG DriverConfig; + GPIO_CLIENT_REGISTRATION_PACKET RegistrationPacket; + NTSTATUS Status; + + // + // Initialize the driver configuration structure. + // + + WDF_DRIVER_CONFIG_INIT(&DriverConfig, RockchipGpioEvtDeviceAdd); + DriverConfig.EvtDriverUnload = RockchipGpioEvtDriverUnload; + + // + // Create a framework driver object to represent our driver. + // + + Status = WdfDriverCreate(DriverObject, + RegistryPath, + WDF_NO_OBJECT_ATTRIBUTES, + &DriverConfig, + &Driver); + + if (!NT_SUCCESS(Status)) { + goto DriverEntryEnd; + } + + // + // Initialize the client driver registration packet. + // + + RtlZeroMemory(&RegistrationPacket, sizeof(GPIO_CLIENT_REGISTRATION_PACKET)); + RegistrationPacket.Version = GPIO_CLIENT_VERSION; + RegistrationPacket.Size = sizeof(GPIO_CLIENT_REGISTRATION_PACKET); + + // + // Initialize the device context size. + // + + RegistrationPacket.ControllerContextSize = sizeof(SIM_GPIO_CONTEXT); + + // + // General interfaces. + // + + RegistrationPacket.CLIENT_PrepareController = RockchipGpioPrepareController; + RegistrationPacket.CLIENT_QueryControllerBasicInformation = + RockchipGpioQueryControllerBasicInformation; + + // + // The query/set handler is required in this sample only if F-state + // power management is enabled to indicate which banks support f-state + // based power maanagement. + // + +#ifdef ENABLE_F_STATE_POWER_MGMT + + RegistrationPacket.CLIENT_QuerySetControllerInformation = + RockchipGpioQuerySetControllerInformation; + +#endif + + RegistrationPacket.CLIENT_StartController = RockchipGpioStartController; + RegistrationPacket.CLIENT_StopController = RockchipGpioStopController; + RegistrationPacket.CLIENT_ReleaseController = RockchipGpioReleaseController; + + // + // Interrupt enable and disable handlers. + // + + RegistrationPacket.CLIENT_DisableInterrupt = RockchipGpioDisableInterrupt; + RegistrationPacket.CLIENT_EnableInterrupt = RockchipGpioEnableInterrupt; + + // + // Interrupt mask, unmask and reconfigure interrupt handlers. + // + + RegistrationPacket.CLIENT_MaskInterrupts = RockchipGpioMaskInterrupts; + RegistrationPacket.CLIENT_UnmaskInterrupt = RockchipGpioUnmaskInterrupt; + RegistrationPacket.CLIENT_ReconfigureInterrupt = RockchipGpioReconfigureInterrupt; + + // + // Handlers to query active/enabled interrupts and clear active interrupts. + // + + RegistrationPacket.CLIENT_ClearActiveInterrupts = RockchipGpioClearActiveInterrupts; + RegistrationPacket.CLIENT_QueryActiveInterrupts = RockchipGpioQueryActiveInterrupts; + RegistrationPacket.CLIENT_QueryEnabledInterrupts = RockchipGpioQueryEnabledInterrupts; + + // + // Handlers for GPIO I/O operations. + // + + RegistrationPacket.CLIENT_ConnectIoPins = RockchipGpioConnectIoPins; + RegistrationPacket.CLIENT_DisconnectIoPins = RockchipGpioDisconnectIoPins; + RegistrationPacket.CLIENT_ReadGpioPinsUsingMask = RockchipGpioReadGpioPins; + RegistrationPacket.CLIENT_WriteGpioPinsUsingMask = RockchipGpioWriteGpioPins; + + // + // Handlers for GPIO save and restore context (if F-state power mgmt is + // supported). + // + +#ifdef ENABLE_F_STATE_POWER_MGMT + + RegistrationPacket.CLIENT_SaveBankHardwareContext = RockchipGpioSaveBankHardwareContext; + RegistrationPacket.CLIENT_RestoreBankHardwareContext = RockchipGpioRestoreBankHardwareContext; + +#endif + + // + // Register the SimGPIO client driver with the GPIO class extension. + // + + Status = GPIO_CLX_RegisterClient(Driver, &RegistrationPacket, RegistryPath); + +DriverEntryEnd: + return Status; +} + +VOID +RockchipGpioEvtDriverUnload ( + _In_ WDFDRIVER Driver + ) + +/*++ + +Routine Description: + + This routine is called by WDF to allow final cleanup prior to unloading the + SimGPIO client driver. This routine unregisters the client driver from the + class extension. + +Arguments: + + Driver - Supplies a handle to a framework driver object. + +Return Value: + + None. + +--*/ + +{ + NTSTATUS Status; + + PAGED_CODE(); + + Status = GPIO_CLX_UnregisterClient(Driver); + NT_ASSERT(NT_SUCCESS(Status)); +} + +NTSTATUS +RockchipGpioEvtDeviceAdd ( + _In_ WDFDRIVER Driver, + _Inout_ PWDFDEVICE_INIT DeviceInit + ) + +/*++ + +Routine Description: + + This routine is the AddDevice entry point for the client driver. This + routine is called by the framework in response to AddDevice call from the + PnP manager. It will create and initialize the device object to represent + a new instance of the simulated GPIO controller. + +Arguments: + + Driver - Supplies a handle to the driver object created in DriverEntry. + + DeviceInit - Supplies a pointer to a framework-allocated WDFDEVICE_INIT + structure. + +Return Value: + + NTSTATUS code. + +--*/ + +{ + WDFDEVICE Device; + WDF_OBJECT_ATTRIBUTES FdoAttributes; + NTSTATUS Status; + + PAGED_CODE(); + + // + // Call the GPIO class extension's pre-device create interface. + // + + Status = GPIO_CLX_ProcessAddDevicePreDeviceCreate(Driver, + DeviceInit, + &FdoAttributes); + if (!NT_SUCCESS(Status)) { + goto EvtDeviceAddEnd; + } + + // + // Call the framework to create the device and attach it to the lower stack. + // + + Status = WdfDeviceCreate(&DeviceInit, &FdoAttributes, &Device); + if (!NT_SUCCESS(Status)) { + goto EvtDeviceAddEnd; + } + + // + // Call the GPIO class extension's post-device create interface. + // + + Status = GPIO_CLX_ProcessAddDevicePostDeviceCreate(Driver, Device); + if (!NT_SUCCESS(Status)) { + goto EvtDeviceAddEnd; + } + +EvtDeviceAddEnd: + return Status; +} + +_IRQL_requires_(PASSIVE_LEVEL) +VOID +RockchipGpiopUnmapControllerBase ( + _In_ PSIM_GPIO_CONTEXT GpioContext + ) + +/*++ + +Routine Description: + + This routine releases the memory mapping for the GPIO controller's + registers, if one has been established. + + N.B. This function is not marked pageable because this function is in + the device power down path. + +Arguments: + + Context - Supplies a pointer to the GPIO client driver's device extension. + +Return Value: + + NTSTATUS code. + +--*/ + +{ + if (GpioContext->VirtualBaseAddress != NULL) { + MmUnmapIoSpace(GpioContext->VirtualBaseAddress, GpioContext->Length); + GpioContext->VirtualBaseAddress = NULL; + } +} + +// +// ---------------------------------------------------------- General intefaces +// + +_Must_inspect_result_ +_IRQL_requires_(PASSIVE_LEVEL) +NTSTATUS +RockchipGpioPrepareController ( + _In_ WDFDEVICE Device, + _In_ PVOID Context, + _In_ WDFCMRESLIST ResourcesRaw, + _In_ WDFCMRESLIST ResourcesTranslated + ) + +/*++ + +Routine Description: + + This routine is called by the GPIO class extension to prepare the + simulated GPIO controller for use. + + N.B. This function is not marked pageable because this function is in + the device power up path. + +Arguments: + + Context - Supplies a pointer to the GPIO client driver's device extension. + + ResourcesRaw - Supplies a handle to a collection of framework resource + objects. This collection identifies the raw (bus-relative) hardware + resources that have been assigned to the device. + + ResourcesTranslated - Supplies a handle to a collection of framework + resource objects. This collection identifies the translated + (system-physical) hardware resources that have been assigned to the + device. The resources appear from the CPU's point of view. + +Return Value: + + NTSTATUS code. + +--*/ + +{ + BANK_ID BankId; + PCM_PARTIAL_RESOURCE_DESCRIPTOR Descriptor; + PROCKCHIP_GPIO_BANK GpioBank; + PSIM_GPIO_CONTEXT GpioContext; + ULONG Index; + ULONG InterruptResourceCount; + ULONG MemoryResourceCount; + ULONG RequiredSize; + ULONG Offset; + ULONG ResourceCount; + NTSTATUS Status; + + UNREFERENCED_PARAMETER(Device); + UNREFERENCED_PARAMETER(ResourcesRaw); + + GpioContext = (PSIM_GPIO_CONTEXT)Context; + RtlZeroMemory(GpioContext, sizeof(SIM_GPIO_CONTEXT)); + GpioContext->TotalPins = SIM_GPIO_TOTAL_PINS; + + // + // Walk through the resource list and map all the resources. Atleast one + // memory resource and one interrupt resource is expected. The resources + // are described in the ACPI namespace. + // + + InterruptResourceCount = 0; + MemoryResourceCount = 0; + ResourceCount = WdfCmResourceListGetCount(ResourcesTranslated); + Status = STATUS_SUCCESS; + for (Index = 0; Index < ResourceCount; Index += 1) { + Descriptor = WdfCmResourceListGetDescriptor(ResourcesTranslated, Index); + switch(Descriptor->Type) { + + // + // The memory resource supplies the physical register base for the GPIO + // controller. Map it virtually as non-cached. + // + + case CmResourceTypeMemory: + if (MemoryResourceCount == 0) { + RequiredSize = SIM_GPIO_TOTAL_BANKS * BANK_SIZE; + + NT_ASSERT(Descriptor->u.Memory.Length >= RequiredSize); + + if (Descriptor->u.Memory.Length < RequiredSize) { + Status = STATUS_UNSUCCESSFUL; + break; + } + + GpioContext->PhysicalBaseAddress = Descriptor->u.Memory.Start; + GpioContext->VirtualBaseAddress = MmMapIoSpaceEx( + Descriptor->u.Memory.Start, + RequiredSize, + PAGE_NOCACHE | PAGE_READWRITE); + + // + // Fail initialization if mapping of the memory region failed. + // + + if (GpioContext->VirtualBaseAddress == NULL) { + Status = STATUS_UNSUCCESSFUL; + } + + GpioContext->Length = RequiredSize; + } + + MemoryResourceCount += 1; + break; + // + // Interrupt resource which supplies the GPIO controller interrupt + // (that connects to the GIC). + // + // N.B. Connecting of the interrupt is handled by the GPIO class + // extension. Only ensure that appropriate number of interrupts + // were described. + // + + case CmResourceTypeInterrupt: + InterruptResourceCount += 1; + break; + + // + // This could be device-private type added by the underlying bus + // driver. Do not filter or alter this resource information. + // + + default: + break; + } + + if (!NT_SUCCESS(Status)) { + goto PrepareControllerEnd; + } + } + + // + // Fail initialization if minimum number of interrupt + // and memory resources were not described correctly. + // + + if ((InterruptResourceCount < SIM_GPIO_TOTAL_BANKS) || + (MemoryResourceCount < 1)) { + Status = STATUS_UNSUCCESSFUL; + goto PrepareControllerEnd; + } + + // + // Initialize the base address of registers per bank. + // + + for (BankId = 0; BankId < SIM_GPIO_TOTAL_BANKS; BankId += 1) { + GpioBank = &GpioContext->Banks[BankId]; + Offset = BankId * BANK_SIZE; + GpioBank->VirtualBaseAddress = Add2Ptr(GpioContext->VirtualBaseAddress, Offset); + GpioBank->Length = BANK_SIZE; + + // Get GPIO Bank Type + UINT32 id = read32(GpioBank, GPIO_REGS_V2.VersionID); + + //If not gpio v2, then default to v1 + if (id == GPIO_TYPE_V2 || id == GPIO_TYPE_V2_1) { + GpioBank->Registers = GPIO_REGS_V2; + GpioBank->GpioType = GPIO_TYPE_V2; + } + else { + GpioBank->Registers = GPIO_REGS_V1; + GpioBank->GpioType = GPIO_TYPE_V1; + } + } + +PrepareControllerEnd: + if (!NT_SUCCESS(Status)) { + RockchipGpiopUnmapControllerBase(GpioContext); + } + return Status; +} + +_Must_inspect_result_ +_IRQL_requires_(PASSIVE_LEVEL) +NTSTATUS +RockchipGpioReleaseController ( + _In_ WDFDEVICE Device, + _In_ PVOID Context + ) + +/*++ + +Routine Description: + + This routine is called by the GPIO class extension to uninitialize the GPIO + controller. + + N.B. This function is not marked pageable because this function is in + the device power down path. + +Arguments: + + Context - Supplies a pointer to the GPIO client driver's device extension. + +Return Value: + + NTSTATUS code. + +--*/ + +{ + UNREFERENCED_PARAMETER(Device); + UNREFERENCED_PARAMETER(Context); + + // + // Release the mappings established in the initialize callback. + // + // N.B. Disconnecting of the interrupt is handled by the GPIO class + // extension. + // + + RockchipGpiopUnmapControllerBase((PSIM_GPIO_CONTEXT)Context); + return STATUS_SUCCESS; +} + +_Must_inspect_result_ +_IRQL_requires_(PASSIVE_LEVEL) +NTSTATUS +RockchipGpioQueryControllerBasicInformation ( + _In_ PVOID Context, + _Out_ PCLIENT_CONTROLLER_BASIC_INFORMATION ControllerInformation + ) + +/*++ + +Routine Description: + + This routine returns the GPIO controller's attributes to the class extension. + + N.B. This function is not marked pageable because this function is in + the device power up path. + +Arguments: + + Context - Supplies a pointer to the GPIO client driver's device extension. + + ControllerInformation - Supplies a pointer to a buffer that receives + controller's information. + +Return Value: + + NTSTATUS code. + +--*/ + +{ + PSIM_GPIO_CONTEXT GpioContext; + + ControllerInformation->Version = GPIO_CONTROLLER_BASIC_INFORMATION_VERSION; + ControllerInformation->Size = sizeof(CLIENT_CONTROLLER_BASIC_INFORMATION); + + // + // Specify the number of pins on the SimGPIO controller. + // + + GpioContext = (PSIM_GPIO_CONTEXT)Context; + ControllerInformation->TotalPins = GpioContext->TotalPins; + ControllerInformation->NumberOfPinsPerBank = SIM_GPIO_PINS_PER_BANK; + + // + // Indicate that the GPIO controller is memory-mapped and thus can be + // manipulated at DIRQL. + // + // N.B. If the GPIO controller is off-SOC behind some serial bus like + // I2C or SPI, then this field must be set to FALSE. + // + + ControllerInformation->Flags.MemoryMappedController = TRUE; + + // + // Indicate that the H/W registers used for I/O can be accessed seperately + // from the registers used for interrupt processing. + // + // N.B.: Setting this flag to TRUE will cause the GPIO class extension + // to optimize I/O processing by skipping the acquisition of + // interrupt-related locks in I/O paths. Make sure the GPIO controller + // H/W can support simultaneous I/O and interrupt processing. Otherwise + // unpredictable race conditions may occur! + // + + ControllerInformation->Flags.IndependentIoHwSupported = TRUE; + + // + // Indicate that status register must be cleared explicitly. + // + + ControllerInformation->Flags.ActiveInterruptsAutoClearOnRead = 0; + + // + // Indicate that the client driver would like to receive IO requests as a + // set of bitmasks as that maps directly to the register operations. + // + + ControllerInformation->Flags.FormatIoRequestsAsMasks = 1; + + // + // Indicate that the GPIO controller does not support controller-level + // D-state power management. + // + + ControllerInformation->Flags.DeviceIdlePowerMgmtSupported = FALSE; + + // + // Note if bank-level F-state power management is supported, then specify + // as such. Note the QuerySetControllerInformation() handler also needs to + // be implemented in this case. + // + +#ifdef ENABLE_F_STATE_POWER_MGMT + + ControllerInformation->Flags.BankIdlePowerMgmtSupported = TRUE; + +#else + + ControllerInformation->Flags.BankIdlePowerMgmtSupported = FALSE; + +#endif + + // + // Note the IdleTimeout parameter does not need to be initialized if + // D-state power management is not supported. + // + // ControllerInformation->IdleTimeout = IdleTimeoutDefaultValue; + // + + // + // Note if the GPIO controller does not support hardware debouncing and + // software-debouncing should be used instead, set the EmulateDebouncing + // flag. + // + // ControllerInformation->Flags.EmulateDebouncing = TRUE; + // + + // + // Indicate that the client driver prefers GPIO class extension ActiveBoth + // emulation. + // + + ControllerInformation->Flags.EmulateActiveBoth = TRUE; + return STATUS_SUCCESS; +} + +_Must_inspect_result_ +_IRQL_requires_(PASSIVE_LEVEL) +NTSTATUS +RockchipGpioQuerySetControllerInformation ( + _In_ PVOID Context, + _In_ PCLIENT_CONTROLLER_QUERY_SET_INFORMATION_INPUT InputBuffer, + _Out_opt_ PCLIENT_CONTROLLER_QUERY_SET_INFORMATION_OUTPUT OutputBuffer + ) + +/*++ + +Routine Description: + + This routine is the generic GPIO query/set handler. Currently it only + supports returning bank power information. + +Arguments: + + Context - Supplies a pointer to the GPIO client driver's device extension. + + InputBuffer - Supplies a pointer to a buffer that receives the parameters + for the query or set operation. + + OutputBuffer - Supplies a pointer to the GPIO class extension allocated + buffer to return the output values. Note on entry, the + OutputBuffer->Size indicates how big the output buffer is. On exit, the + OutputBuffer->Size indicates the filled-in size or required size. + +Return Value: + + NTSTATUS code. + +--*/ + +{ + PPO_FX_COMPONENT_IDLE_STATE F1Parameters; + NTSTATUS Status; + + UNREFERENCED_PARAMETER(Context); + + if((InputBuffer == NULL) || (OutputBuffer == NULL)) { + Status = STATUS_NOT_SUPPORTED; + goto QuerySetControllerInformationEnd; + } + + if (InputBuffer->RequestType != QueryBankPowerInformation) { + Status = STATUS_NOT_SUPPORTED; + goto QuerySetControllerInformationEnd; + } + + // + // Set the version and size of the output buffer. + // + + OutputBuffer->Version = GPIO_BANK_POWER_INFORMATION_OUTPUT_VERSION; + OutputBuffer->Size = sizeof(CLIENT_CONTROLLER_QUERY_SET_INFORMATION_OUTPUT); + + // + // Mark the given bank (InputBuffer->BankPowerInformation.BankId) as + // supporting F1 state. Since all banks support it, the BankId is not + // checked. + // + + OutputBuffer->BankPowerInformation.F1StateSupported = TRUE; + + // + // Supply the attributes for the F1 power state. + // + + F1Parameters = &OutputBuffer->BankPowerInformation.F1IdleStateParameters; + F1Parameters->NominalPower = SIM_GPIO_F1_NOMINAL_POWER; + F1Parameters->ResidencyRequirement = + WDF_ABS_TIMEOUT_IN_SEC(SIM_GPIO_F1_RESIDENCY); + + F1Parameters->TransitionLatency = + WDF_ABS_TIMEOUT_IN_SEC(SIM_GPIO_F1_TRANSITION); + + Status = STATUS_SUCCESS; + +QuerySetControllerInformationEnd: + return Status; +} + + +_Must_inspect_result_ +_IRQL_requires_(PASSIVE_LEVEL) +NTSTATUS +RockchipGpioStartController ( + _In_ PVOID Context, + _In_ BOOLEAN RestoreContext, + _In_ WDF_POWER_DEVICE_STATE PreviousPowerState + ) + +/*++ + +Routine Description: + + This routine starts the simulated GPIO controller. This routine is + responsible for configuring all the pins to their default modes. + + N.B. This function is not marked pageable because this function is in + the device power up path. It is called at PASSIVE_IRQL though. + +Arguments: + + Context - Supplies a pointer to the GPIO client driver's device extension. + + RestoreContext - Supplies a flag that indicates whether the client driver + should restore the GPIO controller state to a previously saved state + or not. + + PreviousPowerState - Supplies the device power state that the device was in + before this transition to D0. + +Return Value: + + NTSTATUS code. + +--*/ + +{ + BANK_ID BankId; + PSIM_GPIO_CONTEXT GpioContext; + GPIO_SAVE_RESTORE_BANK_HARDWARE_CONTEXT_PARAMETERS RestoreParameters; + + UNREFERENCED_PARAMETER(PreviousPowerState); + + // + // Perform all the steps necessary to start the device. + // + + // + // If restore context is FALSE, then this is initial transition into D0 + // power state for this controller. Windows assumes all interrupts start + // disable/masked (like Linux). Rockchip driver only uses masked and keeps + // everything enabled, so set all mask and all enabled before restoring. + // + // If restore context is TRUE, then this is a transition into D0 power + // state from a lower power Dx state. In such case, restore the context + // that was present before the controller transitioned into the lower + // power state. + // + + GpioContext = (PSIM_GPIO_CONTEXT)Context; + + for (BankId = 0; BankId < SIM_GPIO_TOTAL_BANKS; BankId += 1) { + PROCKCHIP_GPIO_BANK GpioBank = &GpioContext->Banks[BankId]; + ROCKCHIP_GPIO_REGISTERS GpioRegisters = GpioBank->Registers; + gpio_write32(GpioBank, GpioRegisters.IntMask, 0xffffffff); + gpio_write32(GpioBank, GpioRegisters.PortEOI, 0xffffffff); + gpio_write32(GpioBank, GpioRegisters.IntEn, 0xffffffff); + } + + if (RestoreContext == TRUE) { + + // + // Restoring the controller state involves restoring the state of + // each SimGPIO bank. + // + + for (BankId = 0; BankId < SIM_GPIO_TOTAL_BANKS; BankId += 1) { + RestoreParameters.BankId = BankId; + RestoreParameters.State = PreviousPowerState; + RockchipGpioRestoreBankHardwareContext(Context, &RestoreParameters); + } + } + return STATUS_SUCCESS; +} + +_IRQL_requires_(PASSIVE_LEVEL) +NTSTATUS +RockchipGpioStopController ( + _In_ PVOID Context, + _In_ BOOLEAN SaveContext, + _In_ WDF_POWER_DEVICE_STATE TargetState + ) + +/*++ + +Routine Description: + + This routine stops the GPIO controller. This routine is responsible for + resetting all the pins to their default modes. + + N.B. This function is not marked pageable because this function is in + the device power down path. + +Arguments: + + Context - Supplies a pointer to the GPIO client driver's device extension. + + SaveContext - Supplies a flag that indicates whether the client driver + should save the GPIO controller state or not. The state may need + to be restored when the controller is restarted. + + TargetState - Supplies the device power state which the device will be put + in once the callback is complete. + +Return Value: + + NTSTATUS code. + +--*/ + +{ + BANK_ID BankId; + PSIM_GPIO_CONTEXT GpioContext; + GPIO_SAVE_RESTORE_BANK_HARDWARE_CONTEXT_PARAMETERS SaveParameters; + + UNREFERENCED_PARAMETER(TargetState); + + // + // Perform all the steps necessary to stop the device. + // + + // + // If save context is FALSE, then this is a final transition into D3/off + // power state. Hence saving of context is not necessary. + // + // If save context is TRUE, then this is a transition into a lower power + // Dx state. In such case, save the context as it will need to be + // restored when the device is brought back to D0 (i.e. ON) power state. + // + + GpioContext = (PSIM_GPIO_CONTEXT)Context; + if (SaveContext == TRUE) { + for (BankId = 0; BankId < SIM_GPIO_TOTAL_BANKS; BankId += 1) { + SaveParameters.BankId = BankId; + SaveParameters.State = TargetState; + RockchipGpioSaveBankHardwareContext(Context, &SaveParameters); + } + } + return STATUS_SUCCESS; +} + +// +// --------------------------------------------------------- Interrupt Handlers +// + +_Must_inspect_result_ +_IRQL_requires_(PASSIVE_LEVEL) +NTSTATUS +RockchipGpioEnableInterrupt ( + _In_ PVOID Context, + _In_ PGPIO_ENABLE_INTERRUPT_PARAMETERS EnableParameters + ) + +/*++ + +Routine Description: + + This routine configures the supplied pin for interrupt. + + N.B. This routine is called from within a regular thread context (i.e., + non-interrupt context) by the class extension. Thus the interrupt lock + needs to be explicitly acquired for memory-mapped GPIO controllers + prior to manipulating any device state that is also affected from a + routine called within the interrupt context. + +Arguments: + + Context - Supplies a pointer to the GPIO client driver's device extension. + + EnableParameters - Supplies a pointer to a structure containing enable + operation parameters. Fields are: + + BankId - Supplies the ID for the GPIO bank. + + PinNumber - Supplies the interrupt line that should be enabled. The pin + number is relative to the bank. + + Flags - Supplies flags controlling the enable operation. Currently + no flags are defined. + + InterruptMode - Supplies the trigger mode (edge or level) configured for + this interrupt when it was enabled. + + Polarity - Supplies the polarity (active low or active high) configured + for this interrupt when it was enabled. + Note: For edge-triggered interrupts, ActiveLow corresponds to the + falling edge; ActiveHigh corresponds to the rising edge. + + PullConfiguration - Supplies the pin pull-up/pull-down configuration. + + DebouceTimeout - Supplies the debounce timeout to be applied. The + field is in 100th of milli-seconds (i.e., 5.84ms will be supplied + as 584). Default value is zero, which implies, no debounce. + + VendorData - Supplies an optional pointer to a buffer containing the + vendor data supplied in the GPIO descriptor. This field will be + NULL if no vendor data was supplied. This buffer is read-only. + + VendorDataLength - Supplies the length of the vendor data buffer. + +Return Value: + + NTSTATUS code. + +Environment: + + Entry IRQL: PASSIVE_LEVEL. + + Synchronization: The GPIO class extension will synchronize this call + against other passive-level interrupt callbacks (e.g. enable/disable/ + unmask) and IO callbacks. + +--*/ + +{ + BANK_ID BankId; + PROCKCHIP_GPIO_BANK GpioBank; + PSIM_GPIO_CONTEXT GpioContext; + PIN_NUMBER PinNumber; + ULONG PinValue; + ROCKCHIP_GPIO_REGISTERS GpioRegisters; + NTSTATUS Status; + + // + // If the polarity is not supported, then bail out. Note the interrupt + // polarity cannot be InterruptActiveBoth as this driver uses ActiveBoth + // emulation. + // + + if ((EnableParameters->Polarity != InterruptActiveHigh) && + (EnableParameters->Polarity != InterruptActiveLow)) { + + Status = STATUS_NOT_SUPPORTED; + goto EnableInterruptEnd; + } + + BankId = EnableParameters->BankId; + PinNumber = EnableParameters->PinNumber; + GpioContext = (PSIM_GPIO_CONTEXT)Context; + GpioBank = &GpioContext->Banks[BankId]; + GpioRegisters = GpioBank->Registers; + Status = STATUS_SUCCESS; + + // + // The interrupt enable register/bitmap may be manipulated from within the + // interrupt context. Hence updates to it must be synchronized using the + // interrupt lock. + // + + GPIO_CLX_AcquireInterruptLock(Context, BankId); + + //Set GPIO Type to Input + PinValue = gpio_read32(GpioBank, GpioRegisters.PortDDR); + PinValue &= ~MASK(PinNumber); + gpio_write32(GpioBank, GpioRegisters.PortDDR, PinValue); + + // + // Set the mode register. If the interrupt is Level then set the bit; + // otherwise, clear it (edge-triggered). + // + + PinValue = gpio_read32(GpioBank, GpioRegisters.IntType); + if (EnableParameters->InterruptMode == LevelSensitive) { + PinValue &= ~MASK(PinNumber); + } else { + PinValue |= MASK(PinNumber); + } + gpio_write32(GpioBank, GpioRegisters.IntType, PinValue); + + // + // Set the polarity register. There are two bits for each pin. If the + // interrupt is ActiveHigh (or Rising-edge) then set it to 0x1. + // + + PinValue = gpio_read32(GpioBank, GpioRegisters.IntPolarity); + switch (EnableParameters->Polarity) { + + case InterruptActiveHigh: + PinValue |= MASK(PinNumber); + break; + + case InterruptActiveLow: + PinValue &= ~MASK(PinNumber); + break; + } + gpio_write32(GpioBank, GpioRegisters.IntPolarity, PinValue); + + // + // Clear the corresponding status bit first to ignore any stale value. + // + PinValue = MASK(PinNumber); + gpio_write32(GpioBank, GpioRegisters.PortEOI, PinValue); + + // + // Enable the interrupt by un-setting the bit in the mask register. + // + + PinValue = gpio_read32(GpioBank, GpioRegisters.IntMask); + PinValue &= ~MASK(PinNumber); + gpio_write32(GpioBank, GpioRegisters.IntMask, PinValue); + + // + // Release the interrupt lock. + // + + GPIO_CLX_ReleaseInterruptLock(Context, BankId); + + Status = STATUS_SUCCESS; + +EnableInterruptEnd: + return Status; +} + +_Must_inspect_result_ +_IRQL_requires_(PASSIVE_LEVEL) +NTSTATUS +RockchipGpioDisableInterrupt ( + _In_ PVOID Context, + _In_ PGPIO_DISABLE_INTERRUPT_PARAMETERS DisableParameters + ) + +/*++ + +Routine Description: + + This routine disables the supplied pin from interrupting. + + This routine is not marked PAGED as it may be called before/after + the boot device is in D0/D3 if boot device has GPIO dependencies. + + N.B. This routine is called from within a regular thread context (i.e., + non-interrupt context) by the class extension. Thus the interrupt lock + needs to be explicitly acquired prior to manipulating the device state. + +Arguments: + + Context - Supplies a pointer to the GPIO client driver's device extension. + + DisableParameters - Supplies a pointer to a structure supplying the + parameters for disabling the interrupt. Fields are: + + BankId - Supplies the ID for the GPIO bank. + + PinNumber - Supplies the interrupt line that should be disabled. The pin + number is relative to the bank. + + Flags - Supplies flags controlling the disable operation. Currently + no flags are defined. + +Return Value: + + NTSTATUS code (STATUS_SUCCESS always for memory-mapped GPIO controllers). + +Environment: + + Entry IRQL: PASSIVE_LEVEL. + + Synchronization: The GPIO class extension will synchronize this call + against other passive-level interrupt callbacks (e.g. enable/disable/ + unmask) and IO callbacks. + +--*/ + +{ + BANK_ID BankId; + PROCKCHIP_GPIO_BANK GpioBank; + PSIM_GPIO_CONTEXT GpioContext; + ULONG MaskValue; + ROCKCHIP_GPIO_REGISTERS GpioRegisters; + + BankId = DisableParameters->BankId; + GpioContext = (PSIM_GPIO_CONTEXT)Context; + GpioBank = &GpioContext->Banks[BankId]; + GpioRegisters = GpioBank->Registers; + + // + // The interrupt mask register may be manipulated from within the + // interrupt context. Hence updates to it must be synchronized using the + // interrupt lock. + // + + GPIO_CLX_AcquireInterruptLock(Context, BankId); + + // + // Disable the interrupt by setting the bit in the interrupt mask + // register. + // + MaskValue = gpio_read32(GpioBank, GpioRegisters.IntMask); + MaskValue |= MASK(DisableParameters->PinNumber); + gpio_write32(GpioBank, GpioRegisters.IntMask, MaskValue); + + GPIO_CLX_ReleaseInterruptLock(Context, BankId); + return STATUS_SUCCESS; +} + +_Must_inspect_result_ +_IRQL_requires_same_ +NTSTATUS +RockchipGpioMaskInterrupts( + _In_ PVOID Context, + _In_ PGPIO_MASK_INTERRUPT_PARAMETERS MaskParameters +) + +/*++ + +Routine Description: + + This routine invokes masks the supplied pin from interrupting. + +Arguments: + + Context - Supplies a pointer to the GPIO client driver's device extension. + + MaskParameters - Supplies a pointer to a structure containing mask + operation parameters. Fields are: + + BankId - Supplies the ID for the GPIO bank. + + PinMask - Supplies a bitmask of pins which should be masked. If a pin + should be masked, then the corresponding bit is set in the bitmask. + + FailedMask - Supplies a bitmask of pins that failed to be masked. If + a pin could not be masked, the bit should be set in this field. + + N.B. This should only be done if for non memory-mapped controllers. + Memory-mapped controllers are never expected to fail this + operation. + +Return Value: + + NTSTATUS code (STATUS_SUCCESS always for memory-mapped GPIO controllers). + +Environment: + + Entry IRQL: DIRQL if the GPIO controller is memory-mapped; PASSIVE_LEVEL + if the controller is behind some serial-bus. + + N.B. For memory-mapped controllers, this routine is called from within + the interrupt context with the interrupt lock acquired by the class + extension. Hence the lock is not re-acquired here. + + Synchronization: The GPIO class extension will synchronize this call + against other query/clear active and enabled interrupts. + Memory-mapped GPIO controllers: + Callbacks invoked at PASSIVE_LEVEL IRQL (e.g. interrupt + enable/disable/unmask or IO operations) may be active. Those + routines should acquire the interrupt lock prior to manipulating + any state accessed from within this routine. + + Serial-accessible GPIO controllers: + This call is synchronized with all other interrupt and IO callbacks. + +--*/ + +{ + PROCKCHIP_GPIO_BANK GpioBank; + PSIM_GPIO_CONTEXT GpioContext; + ULONG MaskValue; + ROCKCHIP_GPIO_REGISTERS GpioRegisters; + + GpioContext = (PSIM_GPIO_CONTEXT)Context; + GpioBank = &GpioContext->Banks[MaskParameters->BankId]; + GpioRegisters = GpioBank->Registers; + + // + // Mask is essentially same as disable on this GPIO controller. The + // difference between the routines is that mask callback is called at DIRQL + // and automatically synchronized with other DIRQL interrupts callbacks. + // + + MaskValue = gpio_read32(GpioBank, GpioRegisters.IntMask); + MaskValue |= MaskParameters->PinMask; + gpio_write32(GpioBank, GpioRegisters.IntMask, MaskValue); + + // + // Set the bitmask of pins that could not be successfully masked. + // Since this is a memory-mapped controller, the mask operation always + // succeeds. + // + + MaskParameters->FailedMask = 0x0; + return STATUS_SUCCESS; +} + +NTSTATUS +RockchipGpioUnmaskInterrupt ( + _In_ PVOID Context, + _In_ PGPIO_ENABLE_INTERRUPT_PARAMETERS UnmaskParameters + ) + +/*++ + +Routine Description: + + This routine invokes unmasks the supplied interrupt pin. + +Arguments: + + Context - Supplies a pointer to the GPIO client driver's device extension. + + UnmaskParameters - Supplies a pointer to a structure containing parameters + for unmasking the interrupt. Fields are: + + BankId - Supplies the ID for the GPIO bank. + + PinNumber - Supplies the interrupt line that should be unmasked. The pin + number is relative to the bank. + + InterruptMode - Supplies the trigger mode (edge or level) configured for + this interrupt when it was enabled. + + Polarity - Supplies the polarity (active low or active high) configured + for this interrupt when it was enabled. + Note: For edge-triggered interrupts, ActiveLow corresponds to the + falling edge; ActiveHigh corresponds to the rising edge. + + PullConfiguration - Supplies the pin pull-up/pull-down configuration. + + DebouceTimeout - Supplies the debounce timeout to be applied. The + field is in 100th of milli-seconds (i.e., 5.84ms will be supplied + as 584). Default value is zero, which implies, no debounce. + + VendorData - NULL. + + VendorDataLength - 0. + + N.B. The VendorData and VendorDataLength are not supplied for unmask + operation (i.e., both fields are zero). + +Return Value: + + NTSTATUS code (STATUS_SUCCESS always for memory-mapped GPIO controllers). + +Environment: + + Entry IRQL: DIRQL if the GPIO controller is memory-mapped; PASSIVE_LEVEL + if the controller is behind some serial-bus. + + N.B. For memory-mapped controllers, this routine is called from within + the interrupt context with the interrupt lock acquired by the class + extension. Hence the lock is not re-acquired here. + + Synchronization: The GPIO class extension will synchronize this call + against other query/clear active and enabled interrupts. + Memory-mapped GPIO controllers: + Callbacks invoked at PASSIVE_LEVEL IRQL (e.g. interrupt + enable/disable/unmask or IO operations) may be active. Those + routines should acquire the interrupt lock prior to manipulating + any state accessed from within this routine. + + Serial-accessible GPIO controllers: + This call is synchronized with all other interrupt and IO callbacks. + +--*/ + +{ + PROCKCHIP_GPIO_BANK GpioBank; + PSIM_GPIO_CONTEXT GpioContext; + ULONG MaskValue; + ROCKCHIP_GPIO_REGISTERS GpioRegisters; + + GpioContext = (PSIM_GPIO_CONTEXT)Context; + GpioBank = &GpioContext->Banks[UnmaskParameters->BankId]; + GpioRegisters = GpioBank->Registers; + + MaskValue = gpio_read32(GpioBank, GpioRegisters.IntMask); + MaskValue &= ~MASK(UnmaskParameters->PinNumber); + gpio_write32(GpioBank, GpioRegisters.IntMask, MaskValue); + return STATUS_SUCCESS; +} + +_Must_inspect_result_ +_IRQL_requires_same_ +NTSTATUS +RockchipGpioQueryActiveInterrupts ( + _In_ PVOID Context, + _In_ PGPIO_QUERY_ACTIVE_INTERRUPTS_PARAMETERS QueryActiveParameters + ) + +/*++ + +Routine Description: + + This routine returns the current set of active interrupts. + +Arguments: + + Context - Supplies a pointer to the GPIO client driver's device extension. + + QueryActiveParameters - Supplies a pointer to a structure containing query + parameters. Fields are: + + BankId - Supplies the ID for the GPIO bank. + + EnabledMask - Supplies a bitmask of pins enabled for interrupts + on the specified GPIO bank. + + ActiveMask - Supplies a bitmask that receives the active interrupt + mask. If a pin is interrupting and set in EnabledMask, then the + corresponding bit is set in the bitmask. + +Return Value: + + NTSTATUS code (STATUS_SUCCESS always for memory-mapped GPIO controllers). + +Environment: + + Entry IRQL: DIRQL if the GPIO controller is memory-mapped; PASSIVE_LEVEL + if the controller is behind some serial-bus. + + N.B. For memory-mapped controllers, this routine is called from within + the interrupt context with the interrupt lock acquired by the class + extension. + + Synchronization: The GPIO class extension will synchronize this call + against other query/clear active and enabled interrupts. + Memory-mapped GPIO controllers: + Callbacks invoked at PASSIVE_LEVEL IRQL (e.g. interrupt + enable/disable/unmask or IO operations) may be active. Those + routines should acquire the interrupt lock prior to manipulating + any state accessed from within this routine. + + Serial-accessible GPIO controllers: + This call is synchronized with all other interrupt and IO callbacks. + +--*/ + +{ + PROCKCHIP_GPIO_BANK GpioBank; + PSIM_GPIO_CONTEXT GpioContext; + ULONG MaskValue; + ROCKCHIP_GPIO_REGISTERS GpioRegisters; + + GpioContext = (PSIM_GPIO_CONTEXT)Context; + GpioBank = &GpioContext->Banks[QueryActiveParameters->BankId]; + GpioRegisters = GpioBank->Registers; + + + MaskValue = gpio_read32(GpioBank, GpioRegisters.IntStatus); + + // + // Return the value of the status register into the + // ActiveMask parameter. + // + QueryActiveParameters->ActiveMask = (ULONG64)MaskValue; + return STATUS_SUCCESS; +} + +_Must_inspect_result_ +_IRQL_requires_same_ +NTSTATUS +RockchipGpioQueryEnabledInterrupts ( + _In_ PVOID Context, + _In_ PGPIO_QUERY_ENABLED_INTERRUPTS_PARAMETERS QueryEnabledParameters + ) + +/*++ + +Routine Description: + + This routine returns the current set of enabled interrupts. + +Arguments: + + Context - Supplies a pointer to the GPIO client driver's device extension. + + QueryEnabledParameters - Supplies a pointer to a structure containing query + parameters. Fields are: + + BankId - Supplies the ID for the GPIO bank. + + EnabledMask - Supplies a bitmask that receives the enabled interrupt + mask. If a pin is enabled, then the corresponding bit is set in the + mask. + +Return Value: + + NTSTATUS code (STATUS_SUCCESS always for memory-mapped GPIO controllers). + +Environment: + + Entry IRQL: DIRQL if the GPIO controller is memory-mapped; PASSIVE_LEVEL + if the controller is behind some serial-bus. + + N.B. For memory-mapped controllers, this routine is called with the + interrupt lock acquired by the class extension, but not always + from within the interrupt context. + + Synchronization: The GPIO class extension will synchronize this call + against other query/clear active and enabled interrupts. + Memory-mapped GPIO controllers: + Callbacks invoked at PASSIVE_LEVEL IRQL (e.g. interrupt + enable/disable/unmask or IO operations) may be active. Those + routines should acquire the interrupt lock prior to manipulating + any state accessed from within this routine. + + Serial-accessible GPIO controllers: + This call is synchronized with all other interrupt and IO callbacks. + +--*/ + +{ + PROCKCHIP_GPIO_BANK GpioBank; + PSIM_GPIO_CONTEXT GpioContext; + ULONG MaskValue; + ROCKCHIP_GPIO_REGISTERS GpioRegisters; + + GpioContext = (PSIM_GPIO_CONTEXT)Context; + GpioBank = &GpioContext->Banks[QueryEnabledParameters->BankId]; + GpioRegisters = GpioBank->Registers; + + // + // Return the current value of the interrupt enable register into the + // EnabledMask parameter. It is strongly preferred that the true state of + // the hardware is returned, rather than a software-cached variable, since + // CLIENT_QueryEnabledInterrupts is used by the class extension to detect + // interrupt storms. + // + MaskValue = ~gpio_read32(GpioBank, GpioRegisters.IntMask); + + // + // Return the inverse value of the mask register into the + // EnabledMask parameter. + // + QueryEnabledParameters->EnabledMask = (ULONG64)MaskValue; + return STATUS_SUCCESS; +} + +_Must_inspect_result_ +_IRQL_requires_same_ +NTSTATUS +RockchipGpioClearActiveInterrupts ( + _In_ PVOID Context, + _In_ PGPIO_CLEAR_ACTIVE_INTERRUPTS_PARAMETERS ClearParameters + ) + +/*++ + +Routine Description: + + This routine clears the GPIO controller's active set of interrupts. + +Arguments: + + Context - Supplies a pointer to the GPIO client driver's device extension. + + ClearParameters - Supplies a pointer to a structure containing clear + operation parameters. Fields are: + + BankId - Supplies the ID for the GPIO bank. + + ClearActiveMask - Supplies a mask of pins which should be marked as + inactive. If a pin should be cleared, then the corresponding bit is + set in the mask. + + FailedMask - Supplies a bitmask of pins that failed to be cleared. If + a pin could not be cleared, the bit should be set in this field. + + N.B. This should only be done if for non memory-mapped controllers. + Memory-mapped controllers are never expected to fail this + operation. + +Return Value: + + NTSTATUS code (STATUS_SUCCESS always for memory-mapped GPIO controllers). + +Environment: + + Entry IRQL: DIRQL if the GPIO controller is memory-mapped; PASSIVE_LEVEL + if the controller is behind some serial-bus. + + N.B. For memory-mapped controllers, this routine is called from within + the interrupt context with the interrupt lock acquired by the class + extension. + + Synchronization: The GPIO class extension will synchronize this call + against other query/clear active and enabled interrupts. + Memory-mapped GPIO controllers: + Callbacks invoked at PASSIVE_LEVEL IRQL (e.g. interrupt + enable/disable/unmask or IO operations) may be active. Those + routines should acquire the interrupt lock prior to manipulating + any state accessed from within this routine. + + Serial-accessible GPIO controllers: + This call is synchronized with all other interrupt and IO callbacks. + +--*/ + +{ + PROCKCHIP_GPIO_BANK GpioBank; + PSIM_GPIO_CONTEXT GpioContext; + ULONG PinValue; + ROCKCHIP_GPIO_REGISTERS GpioRegisters; + + GpioContext = (PSIM_GPIO_CONTEXT)Context; + GpioBank = &GpioContext->Banks[ClearParameters->BankId]; + GpioRegisters = GpioBank->Registers; + + // + // Ack the bits that are set in the ClearActiveMask parameter. + // + + PinValue = (ULONG)ClearParameters->ClearActiveMask; + gpio_write32(GpioBank, GpioRegisters.PortEOI, PinValue); + + // + // Set the bitmask of pins that could not be successfully cleared. + // Since this is a memory-mapped controller, the clear operation always + // succeeds. + // + + ClearParameters->FailedClearMask = 0x0; + return STATUS_SUCCESS; +} + +NTSTATUS +RockchipGpioReconfigureInterrupt ( + _In_ PVOID Context, + _In_ PGPIO_RECONFIGURE_INTERRUPTS_PARAMETERS ReconfigureParameters + ) + +/*++ + +Routine Description: + + This routine reconfigures the interrupt in the specified mode. + + N.B. This routine is called with the interrupt lock acquired by the + class extension. Hence the lock is not re-acquired here. + +Arguments: + + Context - Supplies a pointer to the GPIO client driver's device extension. + + ReconfigureParameters - Supplies a pointer to a structure containing + parameters for reconfiguring the interrupt. Fields are: + + BankId - Supplies the ID for the GPIO bank. + + PinNumber - Supplies the interrupt line that should be reconfigured. + The pin number is relative to the bank. + + InterruptMode - Supplies the trigger mode (edge or level) for the new + configuration. + + Polarity - Supplies the polarity (active low or active high) for the + new configuration. + Note: For edge-triggered interrupts, ActiveLow corresponds to the + falling edge; ActiveHigh corresponds to the rising edge. + +Return Value: + + NTSTATUS code. + +Environment: + + Entry IRQL: DIRQL if the GPIO controller is memory-mapped; PASSIVE_LEVEL + if the controller is behind some serial-bus. + + N.B. For memory-mapped controllers, this routine is called from within + the interrupt context with the interrupt lock acquired by the class + extension. Hence the lock is not re-acquired here. + + Synchronization: The GPIO class extension will synchronize this call + against other query/clear active and enabled interrupts. + Memory-mapped GPIO controllers: + Callbacks invoked at PASSIVE_LEVEL IRQL (e.g. interrupt + enable/disable/unmask or IO operations) may be active. Those + routines should acquire the interrupt lock prior to manipulating + any state accessed from within this routine. + + Serial-accessible GPIO controllers: + This call is synchronized with all other interrupt and IO callbacks. + +--*/ + +{ + BANK_ID BankId; + PROCKCHIP_GPIO_BANK GpioBank; + PSIM_GPIO_CONTEXT GpioContext; + PIN_NUMBER PinNumber; + ULONG PinValue; + ROCKCHIP_GPIO_REGISTERS GpioRegisters; + NTSTATUS Status; + + BankId = ReconfigureParameters->BankId; + PinNumber = ReconfigureParameters->PinNumber; + GpioContext = (PSIM_GPIO_CONTEXT)Context; + GpioBank = &GpioContext->Banks[BankId]; + GpioRegisters = GpioBank->Registers; + Status = STATUS_SUCCESS; + + // + // Clear any stale status bits from the previous configuration. + // + PinValue = MASK(PinNumber); + gpio_write32(GpioBank, GpioRegisters.PortEOI, PinValue); + + //Set GPIO Type to Input + PinValue = gpio_read32(GpioBank, GpioRegisters.PortDDR); + PinValue &= ~MASK(PinNumber); + gpio_write32(GpioBank, GpioRegisters.PortDDR, PinValue); + + // + // Set the mode register. If the interrupt is Level then set the bit; + // otherwise, clear it (edge-triggered). + // + PinValue = gpio_read32(GpioBank, GpioRegisters.IntType); + if (ReconfigureParameters->InterruptMode == LevelSensitive) { + PinValue &= ~MASK(PinNumber); + } + else { + PinValue |= MASK(PinNumber); + } + gpio_write32(GpioBank, GpioRegisters.IntType, PinValue); + + // + // Set the polarity register. There are two bits for each pin. If the + // interrupt is ActiveHigh (or Rising-edge) then set it to 0x1. + // + PinValue = gpio_read32(GpioBank, GpioRegisters.IntPolarity); + switch (ReconfigureParameters->Polarity) { + + case InterruptActiveHigh: + PinValue |= MASK(PinNumber); + break; + + case InterruptActiveLow: + PinValue &= ~MASK(PinNumber); + break; + + default: + + NT_ASSERT(FALSE); + } + gpio_write32(GpioBank, GpioRegisters.IntPolarity, PinValue); + return STATUS_SUCCESS; +} + +// +// --------------------------------------------------------------- I/O Handlers +// + +_Must_inspect_result_ +_IRQL_requires_(PASSIVE_LEVEL) +NTSTATUS +RockchipGpioConnectIoPins ( + _In_ PVOID Context, + _In_ PGPIO_CONNECT_IO_PINS_PARAMETERS ConnectParameters + ) + +/*++ + +Routine Description: + + This routine invokes connects the specified pins for IO. The pins can + be read from if connected for input, or written to if connected for + output. + + N.B. This routine is called at PASSIVE_LEVEL but is not marked as + PAGED_CODE as it could be executed late in the hibernate or + early in resume sequence (or the deep-idle sequence). + +Arguments: + + Context - Supplies a pointer to the GPIO client driver's device extension. + + ConnectParameters - Supplies a pointer to a structure supplying the + parameters for connecting the IO pins. Fields description: + + BankId - Supplies the ID for the GPIO bank. + + PinNumberTable - Supplies an array of pins to be connected for IO. The + pin numbers are 0-based and relative to the GPIO bank. + + PinCount - Supplies the number of pins in the pin number table. + + ConnectMode - Supplies the mode in which the pins should be configured + (viz. input or output). + + ConnectFlags - Supplies the flags controlling the IO setup. Currently + no flags are defined. + + PullConfiguration - Supplies the pin pull-up/pull-down configuration. + + DebouceTimeout - Supplies the debounce timeout to be applied. The + field is in 100th of milli-seconds (i.e., 5.84ms will be supplied + as 584). Default value is zero, which implies, no debounce. + + DriveStrength - Supplies the drive strength to be applied. The value + is in 100th of mA (i.e., 1.21mA will be supplied as 121mA). + + VendorData - Supplies an optional pointer to a buffer containing the + vendor data supplied in the GPIO descriptor. This field will be + NULL if no vendor data was supplied. This buffer is read-only. + + VendorDataLength - Supplies the length of the vendor data buffer. + + ConnectFlags - Supplies the flag to be used for connect operation. + Currently no flags are defined. + +Return Value: + + NT status code. + +Environment: + + Entry IRQL: PASSIVE_LEVEL. + + Synchronization: The GPIO class extension will synchronize this call + against other passive-level interrupt callbacks (e.g. enable/disable/ + unmask) and IO callbacks. + +--*/ + +{ + PROCKCHIP_GPIO_BANK GpioBank; + PSIM_GPIO_CONTEXT GpioContext; + ULONG Index; + PIN_NUMBER PinNumber; + PPIN_NUMBER PinNumberTable; + ULONG PinValue; + ROCKCHIP_GPIO_REGISTERS GpioRegisters; + NTSTATUS Status; + + GpioContext = (PSIM_GPIO_CONTEXT)Context; + GpioBank = &GpioContext->Banks[ConnectParameters->BankId]; + GpioRegisters = GpioBank->Registers; + Status = STATUS_SUCCESS; + + // + // Read the current direction register value. + // + PinValue = gpio_read32(GpioBank, GpioRegisters.PortDDR); + + // + // Walk through all the supplied pins and connect them in the specified + // mode (input or output). + // + + PinNumberTable = ConnectParameters->PinNumberTable; + for (Index = 0; Index < ConnectParameters->PinCount; Index += 1) { + PinNumber = PinNumberTable[Index]; + + // + // If the pins are being connected for input, then clear the bit. + // Otherwise set the bit. + // + + if (ConnectParameters->ConnectMode == ConnectModeInput) { + PinValue &= ~MASK(PinNumber); + + } else if (ConnectParameters->ConnectMode == ConnectModeOutput) { + PinValue |= MASK(PinNumber); + } + } + + gpio_write32(GpioBank, GpioRegisters.PortDDR, PinValue); + return Status; +} + +_Must_inspect_result_ +_IRQL_requires_(PASSIVE_LEVEL) +NTSTATUS +RockchipGpioDisconnectIoPins ( + _In_ PVOID Context, + _In_ PGPIO_DISCONNECT_IO_PINS_PARAMETERS DisconnectParameters + ) + +/*++ + +Routine Description: + + This routine invokes disconnects the specified IO pins. The pins are + put back in their original mode. + + N.B. This routine is called at PASSIVE_LEVEL but is not marked as + PAGED_CODE as it could be executed late in the hibernate or + early in resume sequence (or the deep-idle sequence). + +Arguments: + + Context - Supplies a pointer to the GPIO client driver's device extension. + + DisconnectParameters - Supplies a pointer to a structure containing + disconnect operation parameters. Fields are: + + BankId - Supplies the ID for the GPIO bank. + + PinNumberTable - Supplies an array of pins to be disconnected. The pin + numbers are relative to the GPIO bank. + + PinCount - Supplies the number of pins in the pin number table. + + DisconnectMode - Supplies the mode in which the pins are currently + configured (viz. input or output). + + DisconnectFlags - Supplies the flags controlling the IO setup. Currently + no flags are defined. + +Return Value: + + NTSTATUS code (STATUS_SUCCESS always for memory-mapped GPIO controllers). + +Environment: + + Entry IRQL: PASSIVE_LEVEL. + + Synchronization: The GPIO class extension will synchronize this call + against other passive-level interrupt callbacks (e.g. enable/disable/ + unmask) and IO callbacks. + +--*/ + +{ + PROCKCHIP_GPIO_BANK GpioBank; + PSIM_GPIO_CONTEXT GpioContext; + ULONG Index; + PIN_NUMBER PinNumber; + PPIN_NUMBER PinNumberTable; + ULONG PinValue; + ULONG OutValue; + ROCKCHIP_GPIO_REGISTERS GpioRegisters; + + // + // If the pin configuration should be preserved post disconnect, then + // there is nothing left to do. + // + + if (DisconnectParameters->DisconnectFlags.PreserveConfiguration == 1) { + return STATUS_SUCCESS; + } + + GpioContext = (PSIM_GPIO_CONTEXT)Context; + GpioBank = &GpioContext->Banks[DisconnectParameters->BankId]; + GpioRegisters = GpioBank->Registers; + + // + // Read the current direction register value. + // + PinValue = gpio_read32(GpioBank, GpioRegisters.PortDDR); + OutValue = gpio_read32(GpioBank, GpioRegisters.PortDR); + + // + // Walk through all the supplied pins and disconnect them. On SimGPIO + // controller, all pins are reset to the default mode (output). + // + PinNumberTable = DisconnectParameters->PinNumberTable; + for (Index = 0; Index < DisconnectParameters->PinCount; Index += 1) { + PinNumber = PinNumberTable[Index]; + PinValue |= MASK(PinNumber); + OutValue &= ~MASK(PinNumber); + } + gpio_write32(GpioBank, GpioRegisters.PortDR, OutValue); + gpio_write32(GpioBank, GpioRegisters.PortDDR, PinValue); + return STATUS_SUCCESS; +} + +_Must_inspect_result_ +NTSTATUS +RockchipGpioReadGpioPins ( + _In_ PVOID Context, + _In_ PGPIO_READ_PINS_MASK_PARAMETERS ReadParameters + ) + +/*++ + +Routine Description: + + This routine reads the current values for all the pins. + + As the FormatIoRequestsAsMasks bit was set inside + RockchipGpioQueryControllerInformation(), all this routine needs to do is read + the level register value and return to the GPIO class extension. It will + return the right set of bits to the caller. + + N.B. This routine is called at DIRQL for memory-mapped GPIOs and thus not + marked as PAGED. + +Arguments: + + Context - Supplies a pointer to the GPIO client driver's device extension. + + ReadParameters - Supplies a pointer to a structure containing read + operation parameters. Fields are: + + BankId - Supplies the ID for the GPIO bank. + + PinValues - Supplies a pointer to a variable that receives the current + pin values. + + Flags - Supplies the flag to be used for read operation. Currently + defined flags are: + + WriteConfiguredPins: If set, the read is being done on a set of + pin that were configured for write. In such cases, the + GPIO client driver is expected to read and return the + output register value. + +Return Value: + + NTSTATUS code (STATUS_SUCCESS always for memory-mapped GPIO controllers). + +Environment: + + Entry IRQL: DIRQL if the GPIO controller is memory-mapped; + PASSIVE_LEVEL if the controller is behind some serial-bus. + + Synchronization: The GPIO class extension will synchronize this call + against other passive-level interrupt callbacks (e.g. enable/disable) + and IO callbacks (connect/disconnect). + +--*/ + +{ + PROCKCHIP_GPIO_BANK GpioBank; + PSIM_GPIO_CONTEXT GpioContext; + ULONG PinValue; + ROCKCHIP_GPIO_REGISTERS GpioRegisters; + + GpioContext = (PSIM_GPIO_CONTEXT)Context; + GpioBank = &GpioContext->Banks[ReadParameters->BankId]; + GpioRegisters = GpioBank->Registers; + + // + // Read the current register value. Note the GPIO class may invoke + // the read routine on write-configured pins. In such case the output + // register values should be read. + // + + if (ReadParameters->Flags.WriteConfiguredPins == FALSE) { + PinValue = read32(GpioBank, GpioRegisters.ExtPort); + + } else { + PinValue = gpio_read32(GpioBank, GpioRegisters.PortDR); + } + + *ReadParameters->PinValues = PinValue; + return STATUS_SUCCESS; +} + +_Must_inspect_result_ +NTSTATUS +RockchipGpioWriteGpioPins ( + _In_ PVOID Context, + _In_ PGPIO_WRITE_PINS_MASK_PARAMETERS WriteParameters + ) + +/*++ + +Routine Description: + + This routine sets the current values for the specified pins. This call is + synchronized with the write and connect/disconnect IO calls. + + N.B. This routine is called at DIRQL for memory-mapped GPIOs and thus not + marked as PAGED. + +Arguments: + + Context - Supplies a pointer to the GPIO client driver's device extension. + + WriteParameters - Supplies a pointer to a structure containing write + operation parameters. Fields are: + + BankId - Supplies the ID for the GPIO bank. + + SetMask - Supplies a mask of pins which should be set (0x1). If a pin + should be set, then the corresponding bit is set in the mask. + All bits that are clear in the mask should be left intact. + + ClearMask - Supplies a mask of pins which should be cleared (0x0). If + a pin should be cleared, then the bit is set in the bitmask. All + bits that are clear in the mask should be left intact. + + Flags - Supplies the flag controlling the write operation. Currently + no flags are defined. + +Return Value: + + NTSTATUS code (STATUS_SUCCESS always for memory-mapped GPIO controllers). + +Environment: + + Entry IRQL: DIRQL if the GPIO controller is memory-mapped; + PASSIVE_LEVEL if the controller is behind some serial-bus. + + Synchronization: The GPIO class extension will synchronize this call + against other passive-level interrupt callbacks (e.g. enable/disable) + and IO callbacks (connect/disconnect). + +--*/ + +{ + PROCKCHIP_GPIO_BANK GpioBank; + PSIM_GPIO_CONTEXT GpioContext; + ULONG PinValue; + ROCKCHIP_GPIO_REGISTERS GpioRegisters; + + GpioContext = (PSIM_GPIO_CONTEXT)Context; + GpioBank = &GpioContext->Banks[WriteParameters->BankId]; + GpioRegisters = GpioBank->Registers; + + // + // Read the current level register value. + // + + PinValue = gpio_read32(GpioBank, GpioRegisters.PortDR); + + // + // Set the bits specified in the set mask and clear the ones specified + // in the clear mask. + // + + PinValue |= WriteParameters->SetMask; + PinValue &= ~WriteParameters->ClearMask; + + // + // Write the updated value to the register. + // + + gpio_write32(GpioBank, GpioRegisters.PortDR, PinValue); + return STATUS_SUCCESS; +} + +// +// ------------------------------------------------------- Power mgmt handlers +// + +VOID +RockchipGpioSaveBankHardwareContext ( + _In_ PVOID Context, + _In_ PGPIO_SAVE_RESTORE_BANK_HARDWARE_CONTEXT_PARAMETERS SaveParameters + ) + +/*++ + +Routine Description: + + This routine saves the hardware context for the GPIO controller. + +Arguments: + + Context - Supplies a pointer to the GPIO client driver's device extension. + + SaveRestoreParameters - Supplies a pointer to a structure containing + parameters for the save operation: + + BankId - Supplies the ID for the GPIO bank. + + State - Target F-state the bank will be transitioned into. + + Flags - Supplies flags for the save operation: + CriticalTransition - TRUE if this is due to a critical transition. + +Return Value: + + None. + +--*/ + +{ + PROCKCHIP_GPIO_BANK GpioBank; + PSIM_GPIO_CONTEXT GpioContext; + ROCKCHIP_GPIO_REGISTERS GpioRegisters; + + GpioContext = (PSIM_GPIO_CONTEXT)Context; + GpioBank = &GpioContext->Banks[SaveParameters->BankId]; + GpioRegisters = GpioBank->Registers; + + // + // Copy the contents of the registers into memory. + // + GpioBank->SavedContext.DataRegister = gpio_read32(GpioBank, GpioRegisters.PortDR); + GpioBank->SavedContext.DataDirectionRegister = gpio_read32(GpioBank, GpioRegisters.PortDDR); + GpioBank->SavedContext.IntMask = gpio_read32(GpioBank, GpioRegisters.IntMask); + GpioBank->SavedContext.IntType = gpio_read32(GpioBank, GpioRegisters.IntType); + GpioBank->SavedContext.IntPolarity = gpio_read32(GpioBank, GpioRegisters.IntPolarity); + return; +} + +VOID +RockchipGpioRestoreBankHardwareContext ( + _In_ PVOID Context, + _In_ PGPIO_SAVE_RESTORE_BANK_HARDWARE_CONTEXT_PARAMETERS RestoreParameters + ) + +/*++ + +Routine Description: + + This routine saves the hardware context for the GPIO controller. + +Arguments: + + Context - Supplies a pointer to the GPIO client driver's device extension. + + SaveRestoreParameters - Supplies a pointer to a structure containing + parameters for the restore operation: + + BankId - Supplies the ID for the GPIO bank. + + State - Target F-state the bank will be transitioned into. + + Flags - Supplies flags for the save operation: + CriticalTransition - TRUE if this is due to a critical transition. + +Return Value: + + None. + +--*/ + +{ + PROCKCHIP_GPIO_BANK GpioBank; + PSIM_GPIO_CONTEXT GpioContext; + ULONG PinValue; + ROCKCHIP_GPIO_REGISTERS GpioRegisters; + + GpioContext = (PSIM_GPIO_CONTEXT)Context; + GpioBank = &GpioContext->Banks[RestoreParameters->BankId]; + GpioRegisters = GpioBank->Registers; + + // + // Restore the level register. + // + + PinValue = GpioBank->SavedContext.DataRegister; + gpio_write32(GpioBank, GpioRegisters.PortDR, PinValue); + + // + // Restore the mode, and polarity registers. + // + + PinValue = GpioBank->SavedContext.IntType; + gpio_write32(GpioBank, GpioRegisters.IntType, PinValue); + + PinValue = GpioBank->SavedContext.IntPolarity; + gpio_write32(GpioBank, GpioRegisters.IntPolarity, PinValue); + + // + // Restore the direction register. + // + + PinValue = GpioBank->SavedContext.DataDirectionRegister; + gpio_write32(GpioBank, GpioRegisters.PortDDR, PinValue); + + // + // Take care to restore the mask register only after restoring the + // mode and polarity registers. Otherwise, the interrupt line will get + // sampled when the mask register gets written to with the mode and + // polarity at that point in time (and could cause a spurious interrupt). + // + + PinValue = GpioBank->SavedContext.IntMask; + gpio_write32(GpioBank, GpioRegisters.IntMask, PinValue); + return; +} + +__pragma(warning(default: 4127)) // conditional expression is a constant + + diff --git a/drivers/gpio/rk3xgpio/rk3xgpio.inx b/drivers/gpio/rk3xgpio/rk3xgpio.inx new file mode 100644 index 0000000..639deae --- /dev/null +++ b/drivers/gpio/rk3xgpio/rk3xgpio.inx @@ -0,0 +1,77 @@ +;/*++ +; +;Copyright (c) CoolStar. All rights reserved. +; +;Module Name: +; rk3xgpio.inf +; +;Abstract: +; INF file for installing the RockChip GPIO Controller Driver +; +; +;--*/ + +[Version] +Signature = "$WINDOWS NT$" +Class = System +ClassGuid = {4d36e97d-e325-11ce-bfc1-08002be10318} +Provider = CoolStar +DriverVer = 6/30/2023,1.0.0 +CatalogFile = rk3xgpio.cat +PnpLockdown = 1 + +[DestinationDirs] +DefaultDestDir = 12 + +; ================= Class section ===================== + +[SourceDisksNames] +1 = %DiskId1%,,,"" + +[SourceDisksFiles] +rk3xgpio.sys = 1,, + +;***************************************** +; Rk3xGPIO Install Section +;***************************************** + +[Manufacturer] +%StdMfg%=Standard,NT$ARCH$.10.0...14393 + +; Decorated model section take precedence over undecorated +; ones on XP and later. +[Standard.NT$ARCH$.10.0...14393] +%Rk3xGPIO.DeviceDesc%=Rk3xGPIO_Device, ACPI\RKCP3002 + +[Rk3xGPIO_Device.NT] +CopyFiles=Drivers_Dir + +[Rk3xGPIO_Device.NT.HW] +AddReg=Rk3xGPIO_AddReg + +[Drivers_Dir] +rk3xgpio.sys + +[Rk3xGPIO_AddReg] +; Set to 1 to connect the first interrupt resource found, 0 to leave disconnected +HKR,Settings,"ConnectInterrupt",0x00010001,0 + +;-------------- Service installation +[Rk3xGPIO_Device.NT.Services] +AddService = Rk3xGPIO,%SPSVCINST_ASSOCSERVICE%, Rk3xGPIO_Service_Inst + +; -------------- Rk3xGPIO driver install sections +[Rk3xGPIO_Service_Inst] +DisplayName = %Rk3xGPIO.SVCDESC% +ServiceType = 1 ; SERVICE_KERNEL_DRIVER +StartType = 3 ; SERVICE_DEMAND_START +ErrorControl = 1 ; SERVICE_ERROR_NORMAL +ServiceBinary = %12%\rk3xgpio.sys +LoadOrderGroup = Base + +[Strings] +SPSVCINST_ASSOCSERVICE= 0x00000002 +StdMfg = "Rockchip" +DiskId1 = "RockChip GPIO Controller Installation Disk #1" +Rk3xGPIO.DeviceDesc = "Rockchip GPIO Controller" +Rk3xGPIO.SVCDESC = "RockChip GPIO Service" \ No newline at end of file diff --git a/drivers/gpio/rk3xgpio/rk3xgpio.rc b/drivers/gpio/rk3xgpio/rk3xgpio.rc new file mode 100644 index 0000000..f36d01f --- /dev/null +++ b/drivers/gpio/rk3xgpio/rk3xgpio.rc @@ -0,0 +1,41 @@ +/*++ + +Copyright (c) Microsoft Corporation All Rights Reserved + +Module Name: + + rk3xgpio.rc + +Abstract: + +--*/ + +#include + +#define VER_FILETYPE VFT_DRV +#define VER_FILESUBTYPE VFT2_DRV_SYSTEM +#define VER_FILEDESCRIPTION_STR "Rockchip GPIO Controller" +#define VER_INTERNALNAME_STR "rk3xgpio.sys" +#define VER_ORIGINALFILENAME_STR "rk3xgpio.sys" + +#define VER_LEGALCOPYRIGHT_YEARS "2023" +#define VER_LEGALCOPYRIGHT_STR "Copyright (C) " VER_LEGALCOPYRIGHT_YEARS " CoolStar." + +#define VER_FILEVERSION 1,0,0,0 +#define VER_PRODUCTVERSION_STR "1.0.0.0" +#define VER_PRODUCTVERSION 1,0,0,0 +#define LVER_PRODUCTVERSION_STR L"1.0.0.0" + +#define VER_FILEFLAGSMASK (VS_FF_DEBUG | VS_FF_PRERELEASE) +#ifdef DEBUG +#define VER_FILEFLAGS (VS_FF_DEBUG) +#else +#define VER_FILEFLAGS (0) +#endif + +#define VER_FILEOS VOS_NT_WINDOWS32 + +#define VER_COMPANYNAME_STR "CoolStar" +#define VER_PRODUCTNAME_STR "Rockchip GPIO Controller" + +#include "common.ver" \ No newline at end of file diff --git a/drivers/gpio/rk3xgpio/rk3xgpio.vcxproj b/drivers/gpio/rk3xgpio/rk3xgpio.vcxproj new file mode 100644 index 0000000..9872545 --- /dev/null +++ b/drivers/gpio/rk3xgpio/rk3xgpio.vcxproj @@ -0,0 +1,113 @@ + + + + + Debug + ARM64 + + + Release + ARM64 + + + + {A80FE9DD-C140-40F6-A3F4-55A2A55BFAD4} + $(MSBuildProjectName) + 1 + Debug + Win32 + {157FF815-CA85-49A2-B622-5D57EFAB0A13} + $(LatestTargetPlatformVersion) + + + + Windows10 + False + Desktop + KMDF + WindowsKernelModeDriver10.0 + Driver + + + Windows10 + True + Desktop + KMDF + WindowsKernelModeDriver10.0 + Driver + + + + + + + + + + + + + * + true + $(InfArch) + true + + + + rk3xgpio + + + rk3xgpio + + + + %(AdditionalDependencies);$(DDK_LIB_PATH)\ksguid.lib;$(DDK_LIB_PATH)\ntstrsafe.lib;$(DDK_LIB_PATH)\msgpioclxstub.lib + + + false + Level4 + + + + + sha256 + + + 1.0.0 + + + + + %(AdditionalDependencies);$(DDK_LIB_PATH)\ksguid.lib;$(DDK_LIB_PATH)\ntstrsafe.lib;$(DDK_LIB_PATH)\msgpioclxstub.lib + + + false + Level4 + + + + + sha256 + + + 1.0.0 + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/drivers/gpio/rk3xgpio/rk3xgpio.vcxproj.Filters b/drivers/gpio/rk3xgpio/rk3xgpio.vcxproj.Filters new file mode 100644 index 0000000..143e17d --- /dev/null +++ b/drivers/gpio/rk3xgpio/rk3xgpio.vcxproj.Filters @@ -0,0 +1,36 @@ + + + + + cpp;c;cc;cxx;def;odl;idl;hpj;bat;asm;asmx;* + {E10CB9FB-4852-4353-85F5-667D4D2A13DD} + + + h;hpp;hxx;hm;inl;inc;xsd + {EC8940D8-5E2B-49AB-AB85-7CABCAE698D1} + + + rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav;mfcribbon-ms;man;xml + {42A1FDF4-6DB4-41B2-9423-8002A20D1B0D} + + + inf;inv;inx;mof;mc; + {FD9F921C-D1EF-421B-A692-F23423B32E64} + + + + + Driver Files + + + + + Source Files + + + + + Resource Files + + + \ No newline at end of file diff --git a/drivers/i2c/LICENSE.txt b/drivers/i2c/LICENSE.txt new file mode 100644 index 0000000..3880e06 --- /dev/null +++ b/drivers/i2c/LICENSE.txt @@ -0,0 +1,13 @@ +Copyright 2023 CoolStar + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. \ No newline at end of file diff --git a/drivers/i2c/README.md b/drivers/i2c/README.md new file mode 100644 index 0000000..daaf522 --- /dev/null +++ b/drivers/i2c/README.md @@ -0,0 +1,11 @@ +Rockchip 3xxx I2C Driver + +Tested on RK3588S + +Implements Windows's SPB protocol so most existing I2C drivers should attach and work as child devices off this driver + +Tested operations: +* Connect +* Read +* Write +* Sequence diff --git a/drivers/i2c/rk3xi2c/clocks.c b/drivers/i2c/rk3xi2c/clocks.c new file mode 100644 index 0000000..b45e5d2 --- /dev/null +++ b/drivers/i2c/rk3xi2c/clocks.c @@ -0,0 +1,220 @@ +#include "driver.h" + +static ULONG Rk3xI2CDebugLevel = 100; +static ULONG Rk3xI2CDebugCatagories = DBG_INIT || DBG_PNP || DBG_IOCTL; + +const struct i2c_spec_values* rk3x_i2c_get_spec(unsigned int speed); + +#define DIV_ROUND_UP(n, d) (((n) + (d) - 1) / (d)) + +/** + * rk3x_i2c_v1_calc_timings - Calculate timing values for desired SCL frequency + * @clk_rate: I2C input clock rate + * @t: Known I2C timing information + * @t_calc: Caculated rk3x private timings that would be written into regs + * + * Return: %0 on success, -%EINVAL if the goal SCL rate is too slow. In that case + * a best-effort divider value is returned in divs. If the target rate is + * too high, we silently use the highest possible rate. + * The following formulas are v1's method to calculate timings. + * + * l = divl + 1; + * h = divh + 1; + * s = sda_update_config + 1; + * u = start_setup_config + 1; + * p = stop_setup_config + 1; + * T = Tclk_i2c; + * + * tHigh = 8 * h * T; + * tLow = 8 * l * T; + * + * tHD;sda = (l * s + 1) * T; + * tSU;sda = [(8 - s) * l + 1] * T; + * tI2C = 8 * (l + h) * T; + * + * tSU;sta = (8h * u + 1) * T; + * tHD;sta = [8h * (u + 1) - 1] * T; + * tSU;sto = (8h * p + 1) * T; + */ +NTSTATUS rk3x_i2c_v1_calc_timings(unsigned long clk_rate, + struct i2c_timings* t, + struct rk3x_i2c_calced_timings* t_calc) +{ + unsigned long min_low_ns, min_high_ns; + unsigned long min_setup_start_ns, min_setup_data_ns; + unsigned long min_setup_stop_ns, max_hold_data_ns; + + unsigned long clk_rate_khz, scl_rate_khz; + + unsigned long min_low_div, min_high_div; + + unsigned long min_div_for_hold, min_total_div; + unsigned long extra_div, extra_low_div; + unsigned long sda_update_cfg, stp_sta_cfg, stp_sto_cfg; + + const struct i2c_spec_values* spec; + NTSTATUS status = STATUS_SUCCESS; + + /* Support standard-mode, fast-mode and fast-mode plus */ + if (t->bus_freq_hz > I2C_MAX_FAST_MODE_PLUS_FREQ) + t->bus_freq_hz = I2C_MAX_FAST_MODE_PLUS_FREQ; + + /* prevent scl_rate_khz from becoming 0 */ + if (t->bus_freq_hz < 1000) + t->bus_freq_hz = 1000; + + /* + * min_low_ns: The minimum number of ns we need to hold low to + * meet I2C specification, should include fall time. + * min_high_ns: The minimum number of ns we need to hold high to + * meet I2C specification, should include rise time. + */ + spec = rk3x_i2c_get_spec(t->bus_freq_hz); + + /* calculate min-divh and min-divl */ + clk_rate_khz = DIV_ROUND_UP(clk_rate, 1000); + scl_rate_khz = t->bus_freq_hz / 1000; + min_total_div = DIV_ROUND_UP(clk_rate_khz, scl_rate_khz * 8); + + min_high_ns = t->scl_rise_ns + spec->min_high_ns; + min_high_div = DIV_ROUND_UP(clk_rate_khz * min_high_ns, 8 * 1000000); + + min_low_ns = t->scl_fall_ns + spec->min_low_ns; + min_low_div = DIV_ROUND_UP(clk_rate_khz * min_low_ns, 8 * 1000000); + + /* + * Final divh and divl must be greater than 0, otherwise the + * hardware would not output the i2c clk. + */ + min_high_div = (min_high_div < 1) ? 2 : min_high_div; + min_low_div = (min_low_div < 1) ? 2 : min_low_div; + + /* These are the min dividers needed for min hold times. */ + min_div_for_hold = (min_low_div + min_high_div); + + /* + * This is the maximum divider so we don't go over the maximum. + * We don't round up here (we round down) since this is a maximum. + */ + if (min_div_for_hold >= min_total_div) { + /* + * Time needed to meet hold requirements is important. + * Just use that. + */ + t_calc->div_low = min_low_div; + t_calc->div_high = min_high_div; + } + else { + /* + * We've got to distribute some time among the low and high + * so we don't run too fast. + * We'll try to split things up by the scale of min_low_div and + * min_high_div, biasing slightly towards having a higher div + * for low (spend more time low). + */ + extra_div = min_total_div - min_div_for_hold; + extra_low_div = DIV_ROUND_UP(min_low_div * extra_div, + min_div_for_hold); + + t_calc->div_low = min_low_div + extra_low_div; + t_calc->div_high = min_high_div + (extra_div - extra_low_div); + } + + /* + * calculate sda data hold count by the rules, data_upd_st:3 + * is a appropriate value to reduce calculated times. + */ + for (sda_update_cfg = 3; sda_update_cfg > 0; sda_update_cfg--) { + max_hold_data_ns = DIV_ROUND_UP((sda_update_cfg + * (t_calc->div_low) + 1) + * 1000000, clk_rate_khz); + min_setup_data_ns = DIV_ROUND_UP(((8 - sda_update_cfg) + * (t_calc->div_low) + 1) + * 1000000, clk_rate_khz); + if ((max_hold_data_ns < spec->max_data_hold_ns) && + (min_setup_data_ns > spec->min_data_setup_ns)) + break; + } + + /* calculate setup start config */ + min_setup_start_ns = t->scl_rise_ns + spec->min_setup_start_ns; + stp_sta_cfg = DIV_ROUND_UP(clk_rate_khz * min_setup_start_ns + - 1000000, 8 * 1000000 * (t_calc->div_high)); + + /* calculate setup stop config */ + min_setup_stop_ns = t->scl_rise_ns + spec->min_setup_stop_ns; + stp_sto_cfg = DIV_ROUND_UP(clk_rate_khz * min_setup_stop_ns + - 1000000, 8 * 1000000 * (t_calc->div_high)); + + t_calc->tuning = REG_CON_SDA_CFG(--sda_update_cfg) | + REG_CON_STA_CFG(--stp_sta_cfg) | + REG_CON_STO_CFG(--stp_sto_cfg); + + t_calc->div_low--; + t_calc->div_high--; + + /* Maximum divider supported by hw is 0xffff */ + if (t_calc->div_low > 0xffff) { + t_calc->div_low = 0xffff; + status = STATUS_INVALID_PARAMETER; + } + + if (t_calc->div_high > 0xffff) { + t_calc->div_high = 0xffff; + status = STATUS_INVALID_PARAMETER; + } + + return status; +} + +void rk3x_i2c_adapt_div(PRK3XI2C_CONTEXT pDevice, unsigned long clk_rate) +{ + struct i2c_timings* t = &pDevice->timings; + struct rk3x_i2c_calced_timings calc; + UINT64 t_low_ns, t_high_ns; + UINT32 val; + NTSTATUS status; + + status = pDevice->calcTimings(clk_rate, t, &calc); + if (!NT_SUCCESS(status)) { + Rk3xI2CPrint(DEBUG_LEVEL_ERROR, DBG_PNP, "Could not reach SCL freq %u", t->bus_freq_hz); + } + + WdfInterruptDisable(pDevice->Interrupt); + + val = read32(pDevice, REG_CON); + val &= ~REG_CON_TUNING_MASK; + val |= calc.tuning; + write32(pDevice, REG_CON, val); + write32(pDevice, REG_CLKDIV, + (calc.div_high << 16) | (calc.div_low & 0xffff)); + + WdfInterruptEnable(pDevice->Interrupt); + + t_low_ns = (((UINT64)calc.div_low + 1) * 8 * 1000000000) / clk_rate; + t_high_ns = (((UINT64)calc.div_high + 1) * 8 * 1000000000) / + clk_rate; + Rk3xI2CPrint(DEBUG_LEVEL_ERROR, DBG_PNP, + "CLK %lukhz, Req %uns, Act low %lluns high %lluns\n", + clk_rate / 1000, + 1000000000 / t->bus_freq_hz, + t_low_ns, t_high_ns); +} + +void updateClockSettings(PRK3XI2C_CONTEXT pDevice) { + struct i2c_timings* t = &pDevice->timings; + UINT32 defaultVal; + + defaultVal = t->bus_freq_hz <= I2C_MAX_STANDARD_MODE_FREQ ? 1000 : + t->bus_freq_hz <= I2C_MAX_FAST_MODE_FREQ ? 300 : 120; + t->scl_rise_ns = defaultVal; + + defaultVal = t->bus_freq_hz <= I2C_MAX_FAST_MODE_FREQ ? 300 : 120; + t->scl_fall_ns = defaultVal; + + t->scl_int_delay_ns = 0; + t->sda_fall_ns = t->scl_fall_ns; + t->sda_hold_ns = 0; + t->digital_filter_width_ns = 0; + t->analog_filter_cutoff_freq_hz = 0; +} \ No newline at end of file diff --git a/drivers/i2c/rk3xi2c/driver.h b/drivers/i2c/rk3xi2c/driver.h new file mode 100644 index 0000000..064cab8 --- /dev/null +++ b/drivers/i2c/rk3xi2c/driver.h @@ -0,0 +1,172 @@ +#if !defined(_RK3XI2C_H_) +#define _RK3XI2C_H_ + +#pragma warning(disable:4200) // suppress nameless struct/union warning +#pragma warning(disable:4201) // suppress nameless struct/union warning +#pragma warning(disable:4214) // suppress bit field types other than int warning +#include +#include + +#pragma warning(default:4200) +#pragma warning(default:4201) +#pragma warning(default:4214) +#include +#include +#include + +#define RESHUB_USE_HELPER_ROUTINES +#include +#include + +typedef struct _PNP_I2C_SERIAL_BUS_DESCRIPTOR { + PNP_SERIAL_BUS_DESCRIPTOR SerialBusDescriptor; + ULONG ConnectionSpeed; + USHORT SlaveAddress; + // follwed by optional Vendor Data + // followed by PNP_IO_DESCRIPTOR_RESOURCE_NAME +} PNP_I2C_SERIAL_BUS_DESCRIPTOR, * PPNP_I2C_SERIAL_BUS_DESCRIPTOR; + +#include + +#include "rk3xi2c.h" + +// +// String definitions +// + +#define DRIVERNAME "rk3xi2c.sys: " + +#define RK3XI2C_POOL_TAG (ULONG) 'CIKR' +#define RK3XI2C_HARDWARE_IDS L"CoolStar\\RK3XI2C\0\0" +#define RK3XI2C_HARDWARE_IDS_LENGTH sizeof(RK3XI2C_HARDWARE_IDS) + +#define true 1 +#define false 0 + +/* I2C Frequency Modes */ +#define I2C_MAX_STANDARD_MODE_FREQ 100000 +#define I2C_MAX_FAST_MODE_FREQ 400000 +#define I2C_MAX_FAST_MODE_PLUS_FREQ 1000000 +#define I2C_MAX_TURBO_MODE_FREQ 1400000 +#define I2C_MAX_HIGH_SPEED_MODE_FREQ 3400000 +#define I2C_MAX_ULTRA_FAST_MODE_FREQ 5000000 + +/** + * struct i2c_timings - I2C timing information + * @bus_freq_hz: the bus frequency in Hz + * @scl_rise_ns: time SCL signal takes to rise in ns; t(r) in the I2C specification + * @scl_fall_ns: time SCL signal takes to fall in ns; t(f) in the I2C specification + * @scl_int_delay_ns: time IP core additionally needs to setup SCL in ns + * @sda_fall_ns: time SDA signal takes to fall in ns; t(f) in the I2C specification + * @sda_hold_ns: time IP core additionally needs to hold SDA in ns + * @digital_filter_width_ns: width in ns of spikes on i2c lines that the IP core + * digital filter can filter out + * @analog_filter_cutoff_freq_hz: threshold frequency for the low pass IP core + * analog filter + */ +struct i2c_timings { + UINT32 bus_freq_hz; + UINT32 scl_rise_ns; + UINT32 scl_fall_ns; + UINT32 scl_int_delay_ns; + UINT32 sda_fall_ns; + UINT32 sda_hold_ns; + UINT32 digital_filter_width_ns; + UINT32 analog_filter_cutoff_freq_hz; +}; + +typedef struct _RK3XI2C_CONTEXT +{ + // + // Handle back to the WDFDEVICE + // + + WDFDEVICE FxDevice; + + PVOID MMIOAddress; + SIZE_T MMIOSize; + + //I2C Link + UINT32 baseClock; + PVOID Rk3xI2CBusContext; + WDFIOTARGET busIoTarget; + UINT8 busNumber; + + WDFINTERRUPT Interrupt; + + //Synchronization + WDFWAITLOCK waitLock; + KEVENT waitEvent; + BOOLEAN isBusy; + + //Current Message + PMDL currentMDLChain; + SPB_TRANSFER_DESCRIPTOR currentDescriptor; + UINT8 I2CAddress; + unsigned int mode; + BOOLEAN isLastMsg; + + //State Machine + enum rk3x_i2c_state state; + UINT32 processed; + NTSTATUS transactionStatus; + + //SOC Data + struct i2c_timings timings; + NTSTATUS (*calcTimings)(unsigned long, struct i2c_timings*, struct rk3x_i2c_calced_timings*); +} RK3XI2C_CONTEXT, *PRK3XI2C_CONTEXT; + +WDF_DECLARE_CONTEXT_TYPE_WITH_NAME(RK3XI2C_CONTEXT, GetDeviceContext) + +// +// Function definitions +// + +DRIVER_INITIALIZE DriverEntry; + +EVT_WDF_DRIVER_UNLOAD Rk3xI2CDriverUnload; + +EVT_WDF_DRIVER_DEVICE_ADD Rk3xI2CEvtDeviceAdd; + +EVT_WDF_IO_QUEUE_IO_INTERNAL_DEVICE_CONTROL Rk3xI2CEvtInternalDeviceControl; + +UINT32 read32(PRK3XI2C_CONTEXT pDevice, UINT32 reg); +void write32(PRK3XI2C_CONTEXT pDevice, UINT32 reg, UINT32 val); + +BOOLEAN rk3x_i2c_irq(WDFINTERRUPT Interrupt, ULONG MessageID); +NTSTATUS i2c_xfer(PRK3XI2C_CONTEXT pDevice, + _In_ SPBTARGET SpbTarget, + _In_ SPBREQUEST SpbRequest, + _In_ ULONG TransferCount); +NTSTATUS rk3x_i2c_v1_calc_timings(unsigned long clk_rate, + struct i2c_timings* t, + struct rk3x_i2c_calced_timings* t_calc); +void rk3x_i2c_adapt_div(PRK3XI2C_CONTEXT pDevice, unsigned long clk_rate); +void updateClockSettings(PRK3XI2C_CONTEXT pDevice); + +// +// Helper macros +// + +#define DEBUG_LEVEL_ERROR 1 +#define DEBUG_LEVEL_INFO 2 +#define DEBUG_LEVEL_VERBOSE 3 + +#define DBG_INIT 1 +#define DBG_PNP 2 +#define DBG_IOCTL 4 + +#if 0 +#define Rk3xI2CPrint(dbglevel, dbgcatagory, fmt, ...) { \ + if (Rk3xI2CDebugLevel >= dbglevel && \ + (Rk3xI2CDebugCatagories && dbgcatagory)) \ + { \ + DbgPrint(DRIVERNAME); \ + DbgPrint(fmt, __VA_ARGS__); \ + } \ +} +#else +#define Rk3xI2CPrint(dbglevel, fmt, ...) { \ +} +#endif +#endif \ No newline at end of file diff --git a/drivers/i2c/rk3xi2c/mdlchain.c b/drivers/i2c/rk3xi2c/mdlchain.c new file mode 100644 index 0000000..2e7e4c8 --- /dev/null +++ b/drivers/i2c/rk3xi2c/mdlchain.c @@ -0,0 +1,100 @@ +#include "driver.h" + +NTSTATUS +MdlChainGetByte( + PMDL pMdlChain, + size_t Length, + size_t Index, + UCHAR* pByte) +{ + PMDL mdl = pMdlChain; + size_t mdlByteCount; + size_t currentOffset = Index; + PUCHAR pBuffer; + NTSTATUS status = STATUS_INFO_LENGTH_MISMATCH; + + // Check for out-of-bounds index + + if (Index < Length) { + + while (mdl != NULL) { + + mdlByteCount = MmGetMdlByteCount(mdl); + + if (currentOffset < mdlByteCount) { + + pBuffer = (PUCHAR)MmGetSystemAddressForMdlSafe(mdl, + NormalPagePriority | + MdlMappingNoExecute); + + if (pBuffer != NULL) { + + // Byte found, mark successful + + *pByte = pBuffer[currentOffset]; + status = STATUS_SUCCESS; + } + + break; + } + + currentOffset -= mdlByteCount; + mdl = mdl->Next; + } + + // If after walking the MDL the byte hasn't been found, + // status will still be STATUS_INFO_LENGTH_MISMATCH + } + + return status; +} + +NTSTATUS +MdlChainSetByte( + PMDL pMdlChain, + size_t Length, + size_t Index, + UCHAR Byte +) +{ + PMDL mdl = pMdlChain; + size_t mdlByteCount; + size_t currentOffset = Index; + PUCHAR pBuffer; + NTSTATUS status = STATUS_INFO_LENGTH_MISMATCH; + + // Check for out-of-bounds index + + if (Index < Length) { + + while (mdl != NULL) { + + mdlByteCount = MmGetMdlByteCount(mdl); + + if (currentOffset < mdlByteCount) { + + pBuffer = (PUCHAR)MmGetSystemAddressForMdlSafe(mdl, + NormalPagePriority | + MdlMappingNoExecute); + + if (pBuffer != NULL) { + + // Byte found, mark successful + + pBuffer[currentOffset] = Byte; + status = STATUS_SUCCESS; + } + + break; + } + + currentOffset -= mdlByteCount; + mdl = mdl->Next; + } + + // If after walking the MDL the byte hasn't been found, + // status will still be STATUS_INFO_LENGTH_MISMATCH + } + + return status; +} \ No newline at end of file diff --git a/drivers/i2c/rk3xi2c/rk3xi2c.c b/drivers/i2c/rk3xi2c/rk3xi2c.c new file mode 100644 index 0000000..88e01db --- /dev/null +++ b/drivers/i2c/rk3xi2c/rk3xi2c.c @@ -0,0 +1,551 @@ +#include "driver.h" +#include "stdint.h" + +#define bool int +#define MS_IN_US 1000 + +static ULONG Rk3xI2CDebugLevel = 100; +static ULONG Rk3xI2CDebugCatagories = DBG_INIT || DBG_PNP || DBG_IOCTL; + +UINT32 read32(PRK3XI2C_CONTEXT pDevice, UINT32 reg) +{ + return *(UINT32 *)((CHAR *)pDevice->MMIOAddress + reg); +} + +void write32(PRK3XI2C_CONTEXT pDevice, UINT32 reg, UINT32 val) { + *(UINT32 *)((CHAR *)pDevice->MMIOAddress + reg) = val; +} + +NTSTATUS +DriverEntry( +__in PDRIVER_OBJECT DriverObject, +__in PUNICODE_STRING RegistryPath +) +{ + NTSTATUS status = STATUS_SUCCESS; + WDF_DRIVER_CONFIG config; + WDF_OBJECT_ATTRIBUTES attributes; + + Rk3xI2CPrint(DEBUG_LEVEL_INFO, DBG_INIT, + "Driver Entry\n"); + + WDF_DRIVER_CONFIG_INIT(&config, Rk3xI2CEvtDeviceAdd); + + WDF_OBJECT_ATTRIBUTES_INIT(&attributes); + + // + // Create a framework driver object to represent our driver. + // + + status = WdfDriverCreate(DriverObject, + RegistryPath, + &attributes, + &config, + WDF_NO_HANDLE + ); + + if (!NT_SUCCESS(status)) + { + Rk3xI2CPrint(DEBUG_LEVEL_ERROR, DBG_INIT, + "WdfDriverCreate failed with status 0x%x\n", status); + } + + return status; +} + +static NTSTATUS GetIntegerProperty( + _In_ WDFDEVICE FxDevice, + char* propertyStr, + UINT32* property +) { + PRK3XI2C_CONTEXT pDevice = GetDeviceContext(FxDevice); + WDFMEMORY outputMemory = WDF_NO_HANDLE; + + NTSTATUS status = STATUS_ACPI_NOT_INITIALIZED; + + size_t inputBufferLen = sizeof(ACPI_GET_DEVICE_SPECIFIC_DATA) + strlen(propertyStr) + 1; + ACPI_GET_DEVICE_SPECIFIC_DATA* inputBuffer = ExAllocatePoolWithTag(NonPagedPool, inputBufferLen, RK3XI2C_POOL_TAG); + if (!inputBuffer) { + goto Exit; + } + RtlZeroMemory(inputBuffer, inputBufferLen); + + inputBuffer->Signature = IOCTL_ACPI_GET_DEVICE_SPECIFIC_DATA_SIGNATURE; + + unsigned char uuidend[] = { 0x8a, 0x91, 0xbc, 0x9b, 0xbf, 0x4a, 0xa3, 0x01 }; + + inputBuffer->Section.Data1 = 0xdaffd814; + inputBuffer->Section.Data2 = 0x6eba; + inputBuffer->Section.Data3 = 0x4d8c; + memcpy(inputBuffer->Section.Data4, uuidend, sizeof(uuidend)); //Avoid Windows defender false positive + + strcpy(inputBuffer->PropertyName, propertyStr); + inputBuffer->PropertyNameLength = strlen(propertyStr) + 1; + + PACPI_EVAL_OUTPUT_BUFFER outputBuffer; + size_t outputArgumentBufferSize = 8; + size_t outputBufferSize = FIELD_OFFSET(ACPI_EVAL_OUTPUT_BUFFER, Argument) + sizeof(ACPI_METHOD_ARGUMENT_V1) + outputArgumentBufferSize; + + WDF_OBJECT_ATTRIBUTES attributes; + WDF_OBJECT_ATTRIBUTES_INIT(&attributes); + attributes.ParentObject = FxDevice; + status = WdfMemoryCreate(&attributes, + NonPagedPoolNx, + 0, + outputBufferSize, + &outputMemory, + &outputBuffer); + if (!NT_SUCCESS(status)) { + goto Exit; + } + + WDF_MEMORY_DESCRIPTOR inputMemDesc; + WDF_MEMORY_DESCRIPTOR outputMemDesc; + WDF_MEMORY_DESCRIPTOR_INIT_BUFFER(&inputMemDesc, inputBuffer, (ULONG)inputBufferLen); + WDF_MEMORY_DESCRIPTOR_INIT_HANDLE(&outputMemDesc, outputMemory, NULL); + + status = WdfIoTargetSendInternalIoctlSynchronously( + WdfDeviceGetIoTarget(FxDevice), + NULL, + IOCTL_ACPI_GET_DEVICE_SPECIFIC_DATA, + &inputMemDesc, + &outputMemDesc, + NULL, + NULL + ); + if (!NT_SUCCESS(status)) { + Rk3xI2CPrint( + DEBUG_LEVEL_ERROR, + DBG_IOCTL, + "Error getting device data - 0x%x\n", + status); + goto Exit; + } + + if (outputBuffer->Signature != ACPI_EVAL_OUTPUT_BUFFER_SIGNATURE_V1 && + outputBuffer->Count < 1 && + outputBuffer->Argument->Type != ACPI_METHOD_ARGUMENT_INTEGER && + outputBuffer->Argument->DataLength < 1) { + status = STATUS_ACPI_INVALID_ARGUMENT; + goto Exit; + } + + if (property) { + *property = 0; + RtlCopyMemory(property, outputBuffer->Argument->Data, min(sizeof(*property), outputBuffer->Argument->DataLength)); + } + +Exit: + if (inputBuffer) { + ExFreePoolWithTag(inputBuffer, RK3XI2C_POOL_TAG); + } + if (outputMemory != WDF_NO_HANDLE) { + WdfObjectDelete(outputMemory); + } + return status; +} + +NTSTATUS +OnPrepareHardware( +_In_ WDFDEVICE FxDevice, +_In_ WDFCMRESLIST FxResourcesRaw, +_In_ WDFCMRESLIST FxResourcesTranslated +) +/*++ + +Routine Description: + +This routine caches the SPB resource connection ID. + +Arguments: + +FxDevice - a handle to the framework device object +FxResourcesRaw - list of translated hardware resources that +the PnP manager has assigned to the device +FxResourcesTranslated - list of raw hardware resources that +the PnP manager has assigned to the device + +Return Value: + +Status + +--*/ +{ + PRK3XI2C_CONTEXT pDevice = GetDeviceContext(FxDevice); + NTSTATUS status = STATUS_INSUFFICIENT_RESOURCES; + ULONG resourceCount; + BOOLEAN mmioFound; + + UNREFERENCED_PARAMETER(FxResourcesRaw); + + resourceCount = WdfCmResourceListGetCount(FxResourcesTranslated); + mmioFound = FALSE; + + Rk3xI2CPrint(DEBUG_LEVEL_INFO, DBG_INIT, + "%s\n", __func__); + + for (ULONG i = 0; i < resourceCount; i++) + { + PCM_PARTIAL_RESOURCE_DESCRIPTOR pDescriptor; + + pDescriptor = WdfCmResourceListGetDescriptor( + FxResourcesTranslated, i); + + switch (pDescriptor->Type) + { + case CmResourceTypeMemory: + if (!mmioFound) { + pDevice->MMIOAddress = MmMapIoSpace(pDescriptor->u.Memory.Start, pDescriptor->u.Memory.Length, MmNonCached); + pDevice->MMIOSize = pDescriptor->u.Memory.Length; + + mmioFound = TRUE; + } + break; + } + } + + if (!pDevice->MMIOAddress || !mmioFound) { + status = STATUS_NOT_FOUND; //MMIO is required + return status; + } + + pDevice->busNumber = 0xff; //Set default to -1 + status = GetIntegerProperty(FxDevice, "rockchip,bclk", &pDevice->baseClock); + if (!NT_SUCCESS(status)) { + return status; + } + + Rk3xI2CPrint(DEBUG_LEVEL_INFO, DBG_INIT, + "Base Clock: %d\n", pDevice->baseClock); + + //status = GetIntegerProperty(FxDevice, "rockchip,grf", &pDevice->busNumber); // only seems to be needed for rk3055/rk3188 + pDevice->calcTimings = rk3x_i2c_v1_calc_timings; //for rk3399 / rk356x / rk3588 + + //Set default timings + pDevice->timings.bus_freq_hz = I2C_MAX_STANDARD_MODE_FREQ; + updateClockSettings(pDevice); + return status; +} + +NTSTATUS +OnReleaseHardware( +_In_ WDFDEVICE FxDevice, +_In_ WDFCMRESLIST FxResourcesTranslated +) +/*++ + +Routine Description: + +Arguments: + +FxDevice - a handle to the framework device object +FxResourcesTranslated - list of raw hardware resources that +the PnP manager has assigned to the device + +Return Value: + +Status + +--*/ +{ + NTSTATUS status = STATUS_SUCCESS; + + UNREFERENCED_PARAMETER(FxResourcesTranslated); + + PRK3XI2C_CONTEXT pDevice = GetDeviceContext(FxDevice); + if (pDevice->MMIOAddress) { + MmUnmapIoSpace(pDevice->MMIOAddress, pDevice->MMIOSize); + } + + return status; +} + +NTSTATUS +OnD0Entry( +_In_ WDFDEVICE FxDevice, +_In_ WDF_POWER_DEVICE_STATE FxPreviousState +) +/*++ + +Routine Description: + +This routine allocates objects needed by the driver. + +Arguments: + +FxDevice - a handle to the framework device object +FxPreviousState - previous power state + +Return Value: + +Status + +--*/ +{ + UNREFERENCED_PARAMETER(FxPreviousState); + NTSTATUS status = STATUS_SUCCESS; + PRK3XI2C_CONTEXT pDevice = GetDeviceContext(FxDevice); + + UINT32 version = (read32(pDevice, REG_CON) & (0xff << 16)) >> 16; + Rk3xI2CPrint(DEBUG_LEVEL_INFO, DBG_INIT, + "Version: %d\n", version); + + return status; +} + +NTSTATUS +OnD0Exit( +_In_ WDFDEVICE FxDevice, +_In_ WDF_POWER_DEVICE_STATE FxTargetState +) +/*++ + +Routine Description: + +This routine destroys objects needed by the driver. + +Arguments: + +FxDevice - a handle to the framework device object +FxTargetState - target power state + +Return Value: + +Status + +--*/ +{ + UNREFERENCED_PARAMETER(FxTargetState); + + NTSTATUS status = STATUS_SUCCESS; + + return status; +} + +NTSTATUS OnTargetConnect( + _In_ WDFDEVICE SpbController, + _In_ SPBTARGET SpbTarget +) { + UNREFERENCED_PARAMETER(SpbController); + + PPBC_TARGET pTarget = GetTargetContext(SpbTarget); + + // + // Get target connection parameters. + // + + SPB_CONNECTION_PARAMETERS params; + SPB_CONNECTION_PARAMETERS_INIT(¶ms); + + SpbTargetGetConnectionParameters(SpbTarget, ¶ms); + PRH_QUERY_CONNECTION_PROPERTIES_OUTPUT_BUFFER connection = (PRH_QUERY_CONNECTION_PROPERTIES_OUTPUT_BUFFER)params.ConnectionParameters; + + if (connection->PropertiesLength < sizeof(PNP_SERIAL_BUS_DESCRIPTOR)) { + Rk3xI2CPrint(DEBUG_LEVEL_INFO, DBG_PNP, + "Invalid connection properties (length = %lu, " + "expected = %Iu)\n", + connection->PropertiesLength, + sizeof(PNP_SERIAL_BUS_DESCRIPTOR)); + return STATUS_INVALID_PARAMETER; + } + + PPNP_SERIAL_BUS_DESCRIPTOR descriptor = (PPNP_SERIAL_BUS_DESCRIPTOR)connection->ConnectionProperties; + if (descriptor->SerialBusType != I2C_SERIAL_BUS_TYPE) { + Rk3xI2CPrint(DEBUG_LEVEL_INFO, DBG_PNP, + "Bus type %c not supported, only I2C\n", + descriptor->SerialBusType); + return STATUS_INVALID_PARAMETER; + } + + PPNP_I2C_SERIAL_BUS_DESCRIPTOR i2cDescriptor = (PPNP_I2C_SERIAL_BUS_DESCRIPTOR)connection->ConnectionProperties; + pTarget->Settings.Address = (ULONG)i2cDescriptor->SlaveAddress; + + USHORT I2CFlags = i2cDescriptor->SerialBusDescriptor.TypeSpecificFlags; + + pTarget->Settings.AddressMode = ((I2CFlags & I2C_SERIAL_BUS_SPECIFIC_FLAG_10BIT_ADDRESS) == 0) ? AddressMode7Bit : AddressMode10Bit; + pTarget->Settings.ConnectionSpeed = i2cDescriptor->ConnectionSpeed; + + if (pTarget->Settings.AddressMode != AddressMode7Bit) { + return STATUS_NOT_SUPPORTED; + } + + return STATUS_SUCCESS; +} + +VOID OnSpbIoRead( + _In_ WDFDEVICE SpbController, + _In_ SPBTARGET SpbTarget, + _In_ SPBREQUEST SpbRequest, + _In_ size_t Length +) +{ + PRK3XI2C_CONTEXT pDevice = GetDeviceContext(SpbController); + + NTSTATUS status = i2c_xfer(pDevice, SpbTarget, SpbRequest, 1); + SpbRequestComplete(SpbRequest, status); +} + +VOID OnSpbIoWrite( + _In_ WDFDEVICE SpbController, + _In_ SPBTARGET SpbTarget, + _In_ SPBREQUEST SpbRequest, + _In_ size_t Length +) +{ + PRK3XI2C_CONTEXT pDevice = GetDeviceContext(SpbController); + + NTSTATUS status = i2c_xfer(pDevice, SpbTarget, SpbRequest, 1); + SpbRequestComplete(SpbRequest, status); +} + +VOID OnSpbIoSequence( + _In_ WDFDEVICE SpbController, + _In_ SPBTARGET SpbTarget, + _In_ SPBREQUEST SpbRequest, + _In_ ULONG TransferCount +) +{ + + PRK3XI2C_CONTEXT pDevice = GetDeviceContext(SpbController); + + NTSTATUS status = i2c_xfer(pDevice, SpbTarget, SpbRequest, TransferCount); + SpbRequestComplete(SpbRequest, status); +} + +NTSTATUS +Rk3xI2CEvtDeviceAdd( +IN WDFDRIVER Driver, +IN PWDFDEVICE_INIT DeviceInit +) +{ + NTSTATUS status = STATUS_SUCCESS; + WDF_OBJECT_ATTRIBUTES attributes; + WDFDEVICE device; + PRK3XI2C_CONTEXT devContext; + WDF_INTERRUPT_CONFIG interruptConfig; + + UNREFERENCED_PARAMETER(Driver); + + PAGED_CODE(); + + Rk3xI2CPrint(DEBUG_LEVEL_INFO, DBG_PNP, + "Rk3xI2CEvtDeviceAdd called\n"); + + status = SpbDeviceInitConfig(DeviceInit); + if (!NT_SUCCESS(status)) { + Rk3xI2CPrint(DEBUG_LEVEL_ERROR, DBG_PNP, + "SpbDeviceInitConfig failed with status code 0x%x\n", status); + return status; + } + + { + WDF_PNPPOWER_EVENT_CALLBACKS pnpCallbacks; + WDF_PNPPOWER_EVENT_CALLBACKS_INIT(&pnpCallbacks); + + pnpCallbacks.EvtDevicePrepareHardware = OnPrepareHardware; + pnpCallbacks.EvtDeviceReleaseHardware = OnReleaseHardware; + pnpCallbacks.EvtDeviceD0Entry = OnD0Entry; + pnpCallbacks.EvtDeviceD0Exit = OnD0Exit; + + WdfDeviceInitSetPnpPowerEventCallbacks(DeviceInit, &pnpCallbacks); + } + + // + // Setup the device context + // + + WDF_OBJECT_ATTRIBUTES_INIT_CONTEXT_TYPE(&attributes, RK3XI2C_CONTEXT); + + // + // Create a framework device object.This call will in turn create + // a WDM device object, attach to the lower stack, and set the + // appropriate flags and attributes. + // + + status = WdfDeviceCreate(&DeviceInit, &attributes, &device); + + if (!NT_SUCCESS(status)) + { + Rk3xI2CPrint(DEBUG_LEVEL_ERROR, DBG_PNP, + "WdfDeviceCreate failed with status code 0x%x\n", status); + + return status; + } + + { + WDF_DEVICE_STATE deviceState; + WDF_DEVICE_STATE_INIT(&deviceState); + + deviceState.NotDisableable = WdfFalse; + WdfDeviceSetDeviceState(device, &deviceState); + } + + devContext = GetDeviceContext(device); + + devContext->FxDevice = device; + + // + // Create an interrupt object for hardware notifications + // + WDF_INTERRUPT_CONFIG_INIT( + &interruptConfig, + rk3x_i2c_irq, + NULL); + + status = WdfInterruptCreate( + device, + &interruptConfig, + WDF_NO_OBJECT_ATTRIBUTES, + &devContext->Interrupt); + + if (!NT_SUCCESS(status)) + { + Rk3xI2CPrint(DEBUG_LEVEL_ERROR, DBG_PNP, + "Error creating WDF interrupt object - %!STATUS!", + status); + + return status; + } + + //Create spin lock + status = WdfWaitLockCreate(WDF_NO_OBJECT_ATTRIBUTES, &devContext->waitLock); + if (!NT_SUCCESS(status)) + { + Rk3xI2CPrint(DEBUG_LEVEL_ERROR, DBG_PNP, + "WdfWaitLockCreate failed with status code 0x%x\n", status); + + return status; + } + + //Create Event + KeInitializeEvent(&devContext->waitEvent, NotificationEvent, FALSE); + + // + // Bind a SPB controller object to the device. + // + + SPB_CONTROLLER_CONFIG spbConfig; + SPB_CONTROLLER_CONFIG_INIT(&spbConfig); + + spbConfig.PowerManaged = WdfTrue; + spbConfig.EvtSpbTargetConnect = OnTargetConnect; + spbConfig.EvtSpbIoRead = OnSpbIoRead; + spbConfig.EvtSpbIoWrite = OnSpbIoWrite; + spbConfig.EvtSpbIoSequence = OnSpbIoSequence; + + status = SpbDeviceInitialize(devContext->FxDevice, &spbConfig); + if (!NT_SUCCESS(status)) + { + Rk3xI2CPrint(DEBUG_LEVEL_ERROR, DBG_PNP, + "SpbDeviceInitialize failed with status code 0x%x\n", status); + + return status; + } + + WDF_OBJECT_ATTRIBUTES targetAttributes; + WDF_OBJECT_ATTRIBUTES_INIT_CONTEXT_TYPE(&targetAttributes, PBC_TARGET); + + SpbControllerSetTargetAttributes(devContext->FxDevice, &targetAttributes); + + return status; +} \ No newline at end of file diff --git a/drivers/i2c/rk3xi2c/rk3xi2c.h b/drivers/i2c/rk3xi2c/rk3xi2c.h new file mode 100644 index 0000000..77d93ab --- /dev/null +++ b/drivers/i2c/rk3xi2c/rk3xi2c.h @@ -0,0 +1,204 @@ +#ifndef __CROS_EC_REGS_H__ +#define __CROS_EC_REGS_H__ + +#define BITS_PER_LONG sizeof(LONG) * 8 +#define BITS_PER_LONG_LONG sizeof(LONGLONG) * 8 +#define BIT(nr) (1UL << (nr)) + +#define GENMASK(h, l) (((~0UL) - (1UL << (l)) + 1) & (~0UL >> (BITS_PER_LONG - 1 - (h)))) +#define GENMASK_ULL(h, l) (((~0ULL) - (1ULL << (l)) + 1) & (~0ULL >> (BITS_PER_LONG_LONG - 1 - (h)))) + +typedef enum ADDRESS_MODE +{ + AddressMode7Bit, + AddressMode10Bit +} +ADDRESS_MODE, * PADDRESS_MODE; + +typedef struct PBC_TARGET_SETTINGS +{ + // TODO: Update this structure to include other + // target settings needed to configure the + // controller (i.e. connection speed, phase/ + // polarity for SPI). + + ADDRESS_MODE AddressMode; + USHORT Address; + ULONG ConnectionSpeed; +} +PBC_TARGET_SETTINGS, * PPBC_TARGET_SETTINGS; + +///////////////////////////////////////////////// +// +// Context definitions. +// +///////////////////////////////////////////////// + +typedef struct PBC_TARGET PBC_TARGET, * PPBC_TARGET; + +struct PBC_TARGET +{ + // TODO: Update this structure with variables that + // need to be stored in the target context. + + // Handle to the SPB target. + SPBTARGET SpbTarget; + + // Target specific settings. + PBC_TARGET_SETTINGS Settings; +}; + +WDF_DECLARE_CONTEXT_TYPE_WITH_NAME(PBC_TARGET, GetTargetContext); + +#define I2C_SERIAL_BUS_TYPE 0x01 +#define I2C_SERIAL_BUS_SPECIFIC_FLAG_10BIT_ADDRESS 0x0001 + +/* Register Map */ +#define REG_CON 0x00 /* control register */ +#define REG_CLKDIV 0x04 /* clock divisor register */ +#define REG_MRXADDR 0x08 /* slave address for REGISTER_TX */ +#define REG_MRXRADDR 0x0c /* slave register address for REGISTER_TX */ +#define REG_MTXCNT 0x10 /* number of bytes to be transmitted */ +#define REG_MRXCNT 0x14 /* number of bytes to be received */ +#define REG_IEN 0x18 /* interrupt enable */ +#define REG_IPD 0x1c /* interrupt pending */ +#define REG_FCNT 0x20 /* finished count */ + +/* Data buffer offsets */ +#define TXBUFFER_BASE 0x100 +#define RXBUFFER_BASE 0x200 + +/* REG_CON bits */ +#define REG_CON_EN BIT(0) +enum { + REG_CON_MOD_TX = 0, /* transmit data */ + REG_CON_MOD_REGISTER_TX, /* select register and restart */ + REG_CON_MOD_RX, /* receive data */ + REG_CON_MOD_REGISTER_RX, /* broken: transmits read addr AND writes + * register addr */ +}; +#define REG_CON_MOD(mod) ((mod) << 1) +#define REG_CON_MOD_MASK (BIT(1) | BIT(2)) +#define REG_CON_START BIT(3) +#define REG_CON_STOP BIT(4) +#define REG_CON_LASTACK BIT(5) /* 1: send NACK after last received byte */ +#define REG_CON_ACTACK BIT(6) /* 1: stop if NACK is received */ + +#define REG_CON_TUNING_MASK GENMASK_ULL(15, 8) + +#define REG_CON_SDA_CFG(cfg) ((cfg) << 8) +#define REG_CON_STA_CFG(cfg) ((cfg) << 12) +#define REG_CON_STO_CFG(cfg) ((cfg) << 14) + +/* REG_MRXADDR bits */ +#define REG_MRXADDR_VALID(x) BIT(24 + (x)) /* [x*8+7:x*8] of MRX[R]ADDR valid */ + +/* REG_IEN/REG_IPD bits */ +#define REG_INT_BTF BIT(0) /* a byte was transmitted */ +#define REG_INT_BRF BIT(1) /* a byte was received */ +#define REG_INT_MBTF BIT(2) /* master data transmit finished */ +#define REG_INT_MBRF BIT(3) /* master data receive finished */ +#define REG_INT_START BIT(4) /* START condition generated */ +#define REG_INT_STOP BIT(5) /* STOP condition generated */ +#define REG_INT_NAKRCV BIT(6) /* NACK received */ +#define REG_INT_ALL 0x7f + +/* Constants */ +#define WAIT_TIMEOUT 1000 /* ms */ +#define DEFAULT_SCL_RATE (100 * 1000) /* Hz */ + +/** + * struct i2c_spec_values - I2C specification values for various modes + * @min_hold_start_ns: min hold time (repeated) START condition + * @min_low_ns: min LOW period of the SCL clock + * @min_high_ns: min HIGH period of the SCL cloc + * @min_setup_start_ns: min set-up time for a repeated START conditio + * @max_data_hold_ns: max data hold time + * @min_data_setup_ns: min data set-up time + * @min_setup_stop_ns: min set-up time for STOP condition + * @min_hold_buffer_ns: min bus free time between a STOP and + * START condition + */ +struct i2c_spec_values { + unsigned long min_hold_start_ns; + unsigned long min_low_ns; + unsigned long min_high_ns; + unsigned long min_setup_start_ns; + unsigned long max_data_hold_ns; + unsigned long min_data_setup_ns; + unsigned long min_setup_stop_ns; + unsigned long min_hold_buffer_ns; +}; + +static const struct i2c_spec_values standard_mode_spec = { + .min_hold_start_ns = 4000, + .min_low_ns = 4700, + .min_high_ns = 4000, + .min_setup_start_ns = 4700, + .max_data_hold_ns = 3450, + .min_data_setup_ns = 250, + .min_setup_stop_ns = 4000, + .min_hold_buffer_ns = 4700, +}; + +static const struct i2c_spec_values fast_mode_spec = { + .min_hold_start_ns = 600, + .min_low_ns = 1300, + .min_high_ns = 600, + .min_setup_start_ns = 600, + .max_data_hold_ns = 900, + .min_data_setup_ns = 100, + .min_setup_stop_ns = 600, + .min_hold_buffer_ns = 1300, +}; + +static const struct i2c_spec_values fast_mode_plus_spec = { + .min_hold_start_ns = 260, + .min_low_ns = 500, + .min_high_ns = 260, + .min_setup_start_ns = 260, + .max_data_hold_ns = 400, + .min_data_setup_ns = 50, + .min_setup_stop_ns = 260, + .min_hold_buffer_ns = 500, +}; + +/** + * struct rk3x_i2c_calced_timings - calculated V1 timings + * @div_low: Divider output for low + * @div_high: Divider output for high + * @tuning: Used to adjust setup/hold data time, + * setup/hold start time and setup stop time for + * v1's calc_timings, the tuning should all be 0 + * for old hardware anyone using v0's calc_timings. + */ +struct rk3x_i2c_calced_timings { + unsigned long div_low; + unsigned long div_high; + unsigned int tuning; +}; + +enum rk3x_i2c_state { + STATE_IDLE, + STATE_START, + STATE_READ, + STATE_WRITE, + STATE_STOP +}; + +NTSTATUS +MdlChainGetByte( + PMDL pMdlChain, + size_t Length, + size_t Index, + UCHAR* pByte); + +NTSTATUS +MdlChainSetByte( + PMDL pMdlChain, + size_t Length, + size_t Index, + UCHAR Byte +); + +#endif /* __CROS_EC_REGS_H__ */ \ No newline at end of file diff --git a/drivers/i2c/rk3xi2c/rk3xi2c.inx b/drivers/i2c/rk3xi2c/rk3xi2c.inx new file mode 100644 index 0000000..6b61552 --- /dev/null +++ b/drivers/i2c/rk3xi2c/rk3xi2c.inx @@ -0,0 +1,77 @@ +;/*++ +; +;Copyright (c) CoolStar. All rights reserved. +; +;Module Name: +; rk3xi2c.inf +; +;Abstract: +; INF file for installing the RockChip I2C Controller Driver +; +; +;--*/ + +[Version] +Signature = "$WINDOWS NT$" +Class = System +ClassGuid = {4d36e97d-e325-11ce-bfc1-08002be10318} +Provider = CoolStar +DriverVer = 6/30/2023,1.0.0 +CatalogFile = rk3xi2c.cat +PnpLockdown = 1 + +[DestinationDirs] +DefaultDestDir = 12 + +; ================= Class section ===================== + +[SourceDisksNames] +1 = %DiskId1%,,,"" + +[SourceDisksFiles] +rk3xi2c.sys = 1,, + +;***************************************** +; Rk3xI2C Install Section +;***************************************** + +[Manufacturer] +%StdMfg%=Standard,NT$ARCH$.10.0...14393 + +; Decorated model section take precedence over undecorated +; ones on XP and later. +[Standard.NT$ARCH$.10.0...14393] +%Rk3xI2C.DeviceDesc%=Rk3xI2C_Device, ACPI\RKCP3001 + +[Rk3xI2C_Device.NT] +CopyFiles=Drivers_Dir + +[Rk3xI2C_Device.NT.HW] +AddReg=Rk3xI2C_AddReg + +[Drivers_Dir] +rk3xi2c.sys + +[Rk3xI2C_AddReg] +; Set to 1 to connect the first interrupt resource found, 0 to leave disconnected +HKR,Settings,"ConnectInterrupt",0x00010001,0 + +;-------------- Service installation +[Rk3xI2C_Device.NT.Services] +AddService = Rk3xI2C,%SPSVCINST_ASSOCSERVICE%, Rk3xI2C_Service_Inst + +; -------------- Rk3xI2C driver install sections +[Rk3xI2C_Service_Inst] +DisplayName = %Rk3xI2C.SVCDESC% +ServiceType = 1 ; SERVICE_KERNEL_DRIVER +StartType = 3 ; SERVICE_DEMAND_START +ErrorControl = 1 ; SERVICE_ERROR_NORMAL +ServiceBinary = %12%\rk3xi2c.sys +LoadOrderGroup = Base + +[Strings] +SPSVCINST_ASSOCSERVICE= 0x00000002 +StdMfg = "Rockchip" +DiskId1 = "RockChip I2C Controller Installation Disk #1" +Rk3xI2C.DeviceDesc = "RockChip I2C Controller" +Rk3xI2C.SVCDESC = "RockChip I2C Controller Service" diff --git a/drivers/i2c/rk3xi2c/rk3xi2c.rc b/drivers/i2c/rk3xi2c/rk3xi2c.rc new file mode 100644 index 0000000..0a0d8ee --- /dev/null +++ b/drivers/i2c/rk3xi2c/rk3xi2c.rc @@ -0,0 +1,41 @@ +/*++ + +Copyright (c) Microsoft Corporation All Rights Reserved + +Module Name: + + rk3xi2c.rc + +Abstract: + +--*/ + +#include + +#define VER_FILETYPE VFT_DRV +#define VER_FILESUBTYPE VFT2_DRV_SYSTEM +#define VER_FILEDESCRIPTION_STR "Rockchip I2C Controller" +#define VER_INTERNALNAME_STR "rk3xi2c.sys" +#define VER_ORIGINALFILENAME_STR "rk3xi2c.sys" + +#define VER_LEGALCOPYRIGHT_YEARS "2023" +#define VER_LEGALCOPYRIGHT_STR "Copyright (C) " VER_LEGALCOPYRIGHT_YEARS " CoolStar." + +#define VER_FILEVERSION 1,0,0,0 +#define VER_PRODUCTVERSION_STR "1.0.0.0" +#define VER_PRODUCTVERSION 1,0,0,0 +#define LVER_PRODUCTVERSION_STR L"1.0.0.0" + +#define VER_FILEFLAGSMASK (VS_FF_DEBUG | VS_FF_PRERELEASE) +#ifdef DEBUG +#define VER_FILEFLAGS (VS_FF_DEBUG) +#else +#define VER_FILEFLAGS (0) +#endif + +#define VER_FILEOS VOS_NT_WINDOWS32 + +#define VER_COMPANYNAME_STR "CoolStar" +#define VER_PRODUCTNAME_STR "Rockchip I2C Controller" + +#include "common.ver" \ No newline at end of file diff --git a/drivers/i2c/rk3xi2c/rk3xi2c.vcxproj b/drivers/i2c/rk3xi2c/rk3xi2c.vcxproj new file mode 100644 index 0000000..60c22da --- /dev/null +++ b/drivers/i2c/rk3xi2c/rk3xi2c.vcxproj @@ -0,0 +1,114 @@ + + + + + Debug + ARM64 + + + Release + ARM64 + + + + {B3E71397-9BE4-492B-AAED-4D056E59CB1F} + {1bc93793-694f-48fe-9372-81e2b05556fd} + v4.5 + 11.0 + Win8.1 Debug + Win32 + rk3xi2c + rk3xi2c + $(LatestTargetPlatformVersion) + + + + Windows10 + false + WindowsKernelModeDriver10.0 + Driver + KMDF + + + Windows10 + true + WindowsKernelModeDriver10.0 + Driver + KMDF + + + + + + + + + + + + DbgengKernelDebugger + + + DbgengKernelDebugger + + + + true + trace.h + true + false + $(SPB_INC_PATH)\$(SPB_VERSION_MAJOR).$(SPB_VERSION_MINOR);%(AdditionalIncludeDirectories) + + + 1.0.0 + + + SHA256 + + + $(SPB_LIB_PATH)\$(SPB_VERSION_MAJOR).$(SPB_VERSION_MINOR)\SpbCxStubs.lib;%(AdditionalDependencies) + + + + + true + trace.h + true + false + $(SPB_INC_PATH)\$(SPB_VERSION_MAJOR).$(SPB_VERSION_MINOR);%(AdditionalIncludeDirectories) + + + 1.0.0 + + + SHA256 + + + $(SPB_LIB_PATH)\$(SPB_VERSION_MAJOR).$(SPB_VERSION_MINOR)\SpbCxStubs.lib;%(AdditionalDependencies) + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/drivers/i2c/rk3xi2c/rk3xi2c.vcxproj.Filters b/drivers/i2c/rk3xi2c/rk3xi2c.vcxproj.Filters new file mode 100644 index 0000000..3fe3ecf --- /dev/null +++ b/drivers/i2c/rk3xi2c/rk3xi2c.vcxproj.Filters @@ -0,0 +1,56 @@ + + + + + cpp;c;cc;cxx;def;odl;idl;hpj;bat;asm;asmx;* + {E10CB9FB-4852-4353-85F5-667D4D2A13DD} + + + h;hpp;hxx;hm;inl;inc;xsd + {EC8940D8-5E2B-49AB-AB85-7CABCAE698D1} + + + rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav;mfcribbon-ms;man;xml + {42A1FDF4-6DB4-41B2-9423-8002A20D1B0D} + + + inf;inv;inx;mof;mc; + {FD9F921C-D1EF-421B-A692-F23423B32E64} + + + + + Driver Files + + + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + + + Resource Files + + + + + Header Files + + + Header Files + + + Header Files + + + \ No newline at end of file diff --git a/drivers/i2c/rk3xi2c/rockchipi2c.c b/drivers/i2c/rk3xi2c/rockchipi2c.c new file mode 100644 index 0000000..d769641 --- /dev/null +++ b/drivers/i2c/rk3xi2c/rockchipi2c.c @@ -0,0 +1,443 @@ +#include "driver.h" + +static ULONG Rk3xI2CDebugLevel = 100; +static ULONG Rk3xI2CDebugCatagories = DBG_INIT || DBG_PNP || DBG_IOCTL; + +/* Reset all interrupt pending bits */ +static inline void rk3x_i2c_clean_ipd(PRK3XI2C_CONTEXT pDevice) +{ + write32(pDevice, REG_IPD, REG_INT_ALL); +} + +/** + * rk3x_i2c_start - Generate a START condition, which triggers a REG_INT_START interrupt. + */ +static void rk3x_i2c_start(PRK3XI2C_CONTEXT pDevice) +{ + UINT32 val = read32(pDevice, REG_CON) & REG_CON_TUNING_MASK; + + write32(pDevice, REG_IEN, REG_INT_START); + + /* enable adapter with correct mode, send START condition */ + val |= REG_CON_EN | REG_CON_MOD(pDevice->mode) | REG_CON_START; + + /* if we want to react to NACK, set ACTACK bit */ + val |= REG_CON_ACTACK; + + write32(pDevice, REG_CON, val); +} + +/** + * rk3x_i2c_stop - Generate a STOP condition, which triggers a REG_INT_STOP interrupt. + * @error: Error code to return in rk3x_i2c_xfer + */ +static void rk3x_i2c_stop(PRK3XI2C_CONTEXT pDevice, NTSTATUS status) +{ + unsigned int ctrl; + + pDevice->processed = 0; + pDevice->currentMDLChain = NULL; + RtlZeroBytes(&pDevice->currentDescriptor, sizeof(pDevice->currentDescriptor)); + pDevice->transactionStatus = status; + + if (pDevice->isLastMsg) { + /* Enable stop interrupt */ + write32(pDevice, REG_IEN, REG_INT_STOP); + + pDevice->state = STATE_STOP; + + ctrl = read32(pDevice, REG_CON); + ctrl |= REG_CON_STOP; + write32(pDevice, REG_CON, ctrl); + } + else { + /* Signal rk3x_i2c_xfer to start the next message. */ + pDevice->isBusy = FALSE; + pDevice->state = STATE_IDLE; + + /* + * The HW is actually not capable of REPEATED START. But we can + * get the intended effect by resetting its internal state + * and issuing an ordinary START. + */ + ctrl = read32(pDevice, REG_CON) & REG_CON_TUNING_MASK; + write32(pDevice, REG_CON, ctrl); + + /* signal that we are finished with the current msg */ + Rk3xI2CPrint(DEBUG_LEVEL_ERROR, DBG_IOCTL, "%s: Signal complete\n", __func__); + KeSetEvent(&pDevice->waitEvent, IO_NO_INCREMENT, FALSE); + } +} + +/** + * rk3x_i2c_prepare_read - Setup a read according to i2c->msg + */ +static void rk3x_i2c_prepare_read(PRK3XI2C_CONTEXT pDevice) +{ + unsigned int len = pDevice->currentDescriptor.TransferLength - pDevice->processed; + UINT32 con; + + con = read32(pDevice, REG_CON); + + /* + * The hw can read up to 32 bytes at a time. If we need more than one + * chunk, send an ACK after the last byte of the current chunk. + */ + if (len > 32) { + len = 32; + con &= ~REG_CON_LASTACK; + } + else { + con |= REG_CON_LASTACK; + } + + /* make sure we are in plain RX mode if we read a second chunk */ + if (pDevice->processed != 0) { + con &= ~REG_CON_MOD_MASK; + con |= REG_CON_MOD(REG_CON_MOD_RX); + } + + write32(pDevice, REG_CON, con); + write32(pDevice, REG_MRXCNT, len); +} + +/** + * rk3x_i2c_fill_transmit_buf - Fill the transmit buffer with data from msg + */ +static void rk3x_i2c_fill_transmit_buf(PRK3XI2C_CONTEXT pDevice) +{ + unsigned int i, j; + UINT32 cnt = 0; + UINT32 val; + UINT8 byte; + + for (i = 0; i < 8; ++i) { + val = 0; + for (j = 0; j < 4; ++j) { + if ((pDevice->processed == pDevice->currentDescriptor.TransferLength) && (cnt != 0)) + break; + + if (pDevice->processed == 0 && cnt == 0) + byte = (pDevice->I2CAddress & 0x7f) << 1; + else { + NTSTATUS status = MdlChainGetByte(pDevice->currentMDLChain, + pDevice->currentDescriptor.TransferLength, + pDevice->processed++, + &byte); + if (!NT_SUCCESS(status)) { //Failed to get byte from MDL chain + pDevice->transactionStatus = status; + } + } + + val |= byte << (j * 8); + cnt++; + } + + write32(pDevice, TXBUFFER_BASE + 4 * i, val); + + if (pDevice->processed == pDevice->currentDescriptor.TransferLength) + break; + } + + write32(pDevice, REG_MTXCNT, cnt); +} + +/* IRQ handlers for individual states */ + +static void rk3x_i2c_handle_start(PRK3XI2C_CONTEXT pDevice, unsigned int ipd) +{ + if (!(ipd & REG_INT_START)) { + rk3x_i2c_stop(pDevice, STATUS_IO_DEVICE_ERROR); + Rk3xI2CPrint(DEBUG_LEVEL_ERROR, DBG_IOCTL, "unexpected irq in START: 0x%x\n", ipd); + rk3x_i2c_clean_ipd(pDevice); + return; + } + + /* ack interrupt */ + write32(pDevice, REG_IPD, REG_INT_START); + + /* disable start bit */ + write32(pDevice, REG_CON, read32(pDevice, REG_CON) & ~REG_CON_START); + + /* enable appropriate interrupts and transition */ + if (pDevice->mode == REG_CON_MOD_TX) { + write32(pDevice, REG_IEN, REG_INT_MBTF | REG_INT_NAKRCV); + pDevice->state = STATE_WRITE; + rk3x_i2c_fill_transmit_buf(pDevice); + } + else { + /* in any other case, we are going to be reading. */ + write32(pDevice, REG_IEN, REG_INT_MBRF | REG_INT_NAKRCV); + pDevice->state = STATE_READ; + rk3x_i2c_prepare_read(pDevice); + } +} + +static void rk3x_i2c_handle_write(PRK3XI2C_CONTEXT pDevice, unsigned int ipd) +{ + if (!(ipd & REG_INT_MBTF)) { + rk3x_i2c_stop(pDevice, STATUS_IO_DEVICE_ERROR); + Rk3xI2CPrint(DEBUG_LEVEL_ERROR, DBG_IOCTL, "unexpected irq in WRITE: 0x%x\n", ipd); + rk3x_i2c_clean_ipd(pDevice); + return; + } + + /* ack interrupt */ + write32(pDevice, REG_IPD, REG_INT_MBTF); + + /* are we finished? */ + if (pDevice->processed >= pDevice->currentDescriptor.TransferLength) + rk3x_i2c_stop(pDevice, pDevice->transactionStatus); + else + rk3x_i2c_fill_transmit_buf(pDevice); +} + +static void rk3x_i2c_handle_read(PRK3XI2C_CONTEXT pDevice, unsigned int ipd) +{ + unsigned int i; + unsigned int len = pDevice->currentDescriptor.TransferLength - pDevice->processed; + UINT32 val = 0; + UINT8 byte; + + /* we only care for MBRF here. */ + if (!(ipd & REG_INT_MBRF)) + return; + + /* ack interrupt (read also produces a spurious START flag, clear it too) */ + write32(pDevice, REG_IPD, REG_INT_MBRF | REG_INT_START); + + /* Can only handle a maximum of 32 bytes at a time */ + if (len > 32) + len = 32; + + /* read the data from receive buffer */ + for (i = 0; i < len; ++i) { + if (i % 4 == 0) + val = read32(pDevice, RXBUFFER_BASE + (i / 4) * 4); + + byte = (val >> ((i % 4) * 8)) & 0xff; + NTSTATUS status = MdlChainSetByte(pDevice->currentMDLChain, + pDevice->currentDescriptor.TransferLength, + pDevice->processed++, + byte); + if (!NT_SUCCESS(status)) { + pDevice->transactionStatus = status; + break; + } + } + + /* are we finished? */ + if (pDevice->processed >= pDevice->currentDescriptor.TransferLength) + rk3x_i2c_stop(pDevice, pDevice->transactionStatus); + else + rk3x_i2c_prepare_read(pDevice); +} + +static void rk3x_i2c_handle_stop(PRK3XI2C_CONTEXT pDevice, unsigned int ipd) +{ + unsigned int con; + + if (!(ipd & REG_INT_STOP)) { + rk3x_i2c_stop(pDevice, STATUS_IO_DEVICE_ERROR); + Rk3xI2CPrint(DEBUG_LEVEL_ERROR, DBG_IOCTL, "unexpected irq in STOP: 0x%x\n", ipd); + rk3x_i2c_clean_ipd(pDevice); + return; + } + + /* ack interrupt */ + write32(pDevice, REG_IPD, REG_INT_STOP); + + /* disable STOP bit */ + con = read32(pDevice, REG_CON); + con &= ~REG_CON_STOP; + write32(pDevice, REG_CON, con); + + pDevice->isBusy = FALSE; + pDevice->state = STATE_IDLE; + + /* signal rk3x_i2c_xfer that we are finished */ + Rk3xI2CPrint(DEBUG_LEVEL_ERROR, DBG_IOCTL, "%s: Signal complete\n", __func__); + KeSetEvent(&pDevice->waitEvent, IO_NO_INCREMENT, FALSE); +} + +BOOLEAN rk3x_i2c_irq(WDFINTERRUPT Interrupt, ULONG MessageID) +{ + UNREFERENCED_PARAMETER(MessageID); + + WDFDEVICE Device = WdfInterruptGetDevice(Interrupt); + PRK3XI2C_CONTEXT pDevice = GetDeviceContext(Device); + + unsigned int ipd; + + ipd = read32(pDevice, REG_IPD); + if (pDevice->state == STATE_IDLE) { + Rk3xI2CPrint(DEBUG_LEVEL_ERROR, DBG_IOCTL, "irq in STATE_IDLE, ipd = 0x%x\n", ipd); + rk3x_i2c_clean_ipd(pDevice); + goto out; + } + + Rk3xI2CPrint(DEBUG_LEVEL_INFO, DBG_IOCTL, "IRQ: state %d, ipd: %x\n", pDevice->state, ipd); + + /* Clean interrupt bits we don't care about */ + ipd &= ~(REG_INT_BRF | REG_INT_BTF); + + if (ipd & REG_INT_NAKRCV) { + /* + * We got a NACK in the last operation. Depending on whether + * IGNORE_NAK is set, we have to stop the operation and report + * an error. + */ + write32(pDevice, REG_IPD, REG_INT_NAKRCV); + + ipd &= ~REG_INT_NAKRCV; + + rk3x_i2c_stop(pDevice, STATUS_INVALID_TRANSACTION); + } + + /* is there anything left to handle? */ + if ((ipd & REG_INT_ALL) == 0) + goto out; + + switch (pDevice->state) { + case STATE_START: + rk3x_i2c_handle_start(pDevice, ipd); + break; + case STATE_WRITE: + rk3x_i2c_handle_write(pDevice, ipd); + break; + case STATE_READ: + rk3x_i2c_handle_read(pDevice, ipd); + break; + case STATE_STOP: + rk3x_i2c_handle_stop(pDevice, ipd); + break; + case STATE_IDLE: + break; + } + +out: + return TRUE; +} + +/** + * rk3x_i2c_get_spec - Get timing values of I2C specification + * @speed: Desired SCL frequency + * + * Return: Matched i2c_spec_values. + */ +const struct i2c_spec_values* rk3x_i2c_get_spec(unsigned int speed) +{ + if (speed <= I2C_MAX_STANDARD_MODE_FREQ) + return &standard_mode_spec; + else if (speed <= I2C_MAX_FAST_MODE_FREQ) + return &fast_mode_spec; + else + return &fast_mode_plus_spec; +} + +NTSTATUS i2c_xfer_single( + PRK3XI2C_CONTEXT pDevice, + PPBC_TARGET pTarget, + SPB_TRANSFER_DESCRIPTOR descriptor, + PMDL mdlChain +) { + NTSTATUS status = STATUS_SUCCESS; + + //Setup transaction + if (pTarget->Settings.AddressMode != AddressMode7Bit) { + return STATUS_INVALID_ADDRESS; //No support for 10 bit + } + + UINT32 addr = (pTarget->Settings.Address & 0x7f) << 1; + + KeClearEvent(&pDevice->waitEvent); + + if (descriptor.Direction == SpbTransferDirectionFromDevice) { //xfer read + addr |= 1; /* set read bit */ + + /* + * We have to transmit the target addr first. Use + * MOD_REGISTER_TX for that purpose. + */ + pDevice->mode = REG_CON_MOD_REGISTER_TX; + write32(pDevice, REG_MRXADDR, + addr | REG_MRXADDR_VALID(0)); + write32(pDevice, REG_MRXRADDR, 0); + } + else { + pDevice->mode = REG_CON_MOD_TX; + } + + pDevice->currentDescriptor = descriptor; + pDevice->currentMDLChain = mdlChain; + + pDevice->isLastMsg = FALSE; + pDevice->I2CAddress = pTarget->Settings.Address; + pDevice->isBusy = TRUE; + pDevice->state = STATE_START; + pDevice->processed = 0; + pDevice->transactionStatus = STATUS_SUCCESS; + + rk3x_i2c_start(pDevice); + + LARGE_INTEGER Timeout; + Timeout.QuadPart = -10 * 100 * WAIT_TIMEOUT; + status = KeWaitForSingleObject(&pDevice->waitEvent, Executive, KernelMode, TRUE, &Timeout); //Wait for interrupt + + if (status == STATUS_TIMEOUT) { + Rk3xI2CPrint(DEBUG_LEVEL_ERROR, DBG_IOCTL, "timeout, ipd: 0x%02x, state: %d\n", + read32(pDevice, REG_IPD), pDevice->state); + + /* Force a STOP condition without interrupt */ + write32(pDevice, REG_IEN, 0); + UINT32 val = read32(pDevice, REG_CON) & REG_CON_TUNING_MASK; + val |= REG_CON_EN | REG_CON_STOP; + write32(pDevice, REG_CON, val); + + pDevice->state = STATE_IDLE; + + status = STATUS_IO_TIMEOUT; + return status; + } + + return status; +} + +NTSTATUS i2c_xfer(PRK3XI2C_CONTEXT pDevice, + _In_ SPBTARGET SpbTarget, + _In_ SPBREQUEST SpbRequest, + _In_ ULONG TransferCount) { + NTSTATUS status = STATUS_SUCCESS; + PPBC_TARGET pTarget = GetTargetContext(SpbTarget); + + UINT32 transferredLength = 0; + + WdfWaitLockAcquire(pDevice->waitLock, NULL); + + //Set I2C Clocks + pDevice->timings.bus_freq_hz = pTarget->Settings.ConnectionSpeed; + updateClockSettings(pDevice); + rk3x_i2c_adapt_div(pDevice, pDevice->baseClock); + + SPB_TRANSFER_DESCRIPTOR descriptor; + for (int i = 0; i < TransferCount; i++) { + PMDL mdlChain; + + SPB_TRANSFER_DESCRIPTOR_INIT(&descriptor); + SpbRequestGetTransferParameters(SpbRequest, + i, + &descriptor, + &mdlChain); + + status = i2c_xfer_single(pDevice, pTarget, descriptor, mdlChain); + if (!NT_SUCCESS(status)) + break; + + transferredLength += descriptor.TransferLength; + WdfRequestSetInformation(SpbRequest, transferredLength); + } + + WdfWaitLockRelease(pDevice->waitLock); + + return status; +} \ No newline at end of file diff --git a/drivers/i2c/rk3xi2c/trace.h b/drivers/i2c/rk3xi2c/trace.h new file mode 100644 index 0000000..19b0bdf --- /dev/null +++ b/drivers/i2c/rk3xi2c/trace.h @@ -0,0 +1,43 @@ +#include + +#ifndef _TRACE_H_ +#define _TRACE_H_ + +extern "C" +{ + // + // Tracing Definitions: + // + // Control GUID: + // {73e3b785-f5fb-423e-94a9-56627fea9053} + // + +#define WPP_CONTROL_GUIDS \ + WPP_DEFINE_CONTROL_GUID( \ + SpbTestToolTraceGuid, \ + (73e3b785,f5fb,423e,94a9,56627fea9053), \ + WPP_DEFINE_BIT(TRACE_FLAG_WDFLOADING) \ + WPP_DEFINE_BIT(TRACE_FLAG_SPBAPI) \ + WPP_DEFINE_BIT(TRACE_FLAG_OTHER) \ + ) +} + +#define WPP_LEVEL_FLAGS_LOGGER(level,flags) WPP_LEVEL_LOGGER(flags) +#define WPP_LEVEL_FLAGS_ENABLED(level, flags) (WPP_LEVEL_ENABLED(flags) && WPP_CONTROL(WPP_BIT_ ## flags).Level >= level) + +#define Trace CyapaPrint +#define FuncEntry +#define FuncExit +#define WPP_INIT_TRACING +#define WPP_CLEANUP +#define TRACE_FLAG_SPBAPI 0 +#define TRACE_FLAG_WDFLOADING 0 + +// begin_wpp config +// FUNC FuncEntry{LEVEL=TRACE_LEVEL_VERBOSE}(FLAGS); +// FUNC FuncExit{LEVEL=TRACE_LEVEL_VERBOSE}(FLAGS); +// USEPREFIX(FuncEntry, "%!STDPREFIX! [%!FUNC!] --> entry"); +// USEPREFIX(FuncExit, "%!STDPREFIX! [%!FUNC!] <--"); +// end_wpp + +#endif _TRACE_H_