summaryrefslogtreecommitdiff
path: root/Omap35xxPkg/PciEmulation/PciEmulation.c
diff options
context:
space:
mode:
authorandrewfish <andrewfish@6f19259b-4bc3-4df7-8a09-765794883524>2010-01-28 21:32:01 +0000
committerandrewfish <andrewfish@6f19259b-4bc3-4df7-8a09-765794883524>2010-01-28 21:32:01 +0000
commita3f98646f68239bf9c577b24689bc69cbcde1b47 (patch)
tree5aadfc72e0170e433b621de4da245a906c8c13f6 /Omap35xxPkg/PciEmulation/PciEmulation.c
parent759f21f1d17ce52a05eea31e9ddd1efb4ec68bbb (diff)
downloadedk2-a3f98646f68239bf9c577b24689bc69cbcde1b47.zip
edk2-a3f98646f68239bf9c577b24689bc69cbcde1b47.tar.gz
edk2-a3f98646f68239bf9c577b24689bc69cbcde1b47.tar.bz2
Moving OMAP 3530 code out of BeagleBoard package into its own package
git-svn-id: https://edk2.svn.sourceforge.net/svnroot/edk2/trunk/edk2@9854 6f19259b-4bc3-4df7-8a09-765794883524
Diffstat (limited to 'Omap35xxPkg/PciEmulation/PciEmulation.c')
-rw-r--r--Omap35xxPkg/PciEmulation/PciEmulation.c578
1 files changed, 578 insertions, 0 deletions
diff --git a/Omap35xxPkg/PciEmulation/PciEmulation.c b/Omap35xxPkg/PciEmulation/PciEmulation.c
new file mode 100644
index 0000000..fa44570
--- /dev/null
+++ b/Omap35xxPkg/PciEmulation/PciEmulation.c
@@ -0,0 +1,578 @@
+/** @file
+
+ Copyright (c) 2008-2009, Apple Inc. All rights reserved.
+
+ All rights reserved. This program and the accompanying materials
+ are licensed and made available under the terms and conditions of the BSD License
+ which accompanies this distribution. The full text of the license may be found at
+ http://opensource.org/licenses/bsd-license.php
+
+ THE PROGRAM IS DISTRIBUTED UNDER THE BSD LICENSE ON AN "AS IS" BASIS,
+ WITHOUT WARRANTIES OR REPRESENTATIONS OF ANY KIND, EITHER EXPRESS OR IMPLIED.
+
+**/
+
+#include "PciEmulation.h"
+#include <Omap3530/Omap3530.h>
+
+EFI_CPU_ARCH_PROTOCOL *gCpu;
+EMBEDDED_EXTERNAL_DEVICE *gTPS65950;
+
+#define HOST_CONTROLLER_OPERATION_REG_SIZE 0x44
+
+typedef struct {
+ ACPI_HID_DEVICE_PATH AcpiDevicePath;
+ PCI_DEVICE_PATH PciDevicePath;
+ EFI_DEVICE_PATH_PROTOCOL EndDevicePath;
+} EFI_PCI_IO_DEVICE_PATH;
+
+typedef struct {
+ UINT32 Signature;
+ EFI_PCI_IO_DEVICE_PATH DevicePath;
+ EFI_PCI_IO_PROTOCOL PciIoProtocol;
+ PCI_TYPE00 *ConfigSpace;
+ PCI_ROOT_BRIDGE RootBridge;
+ UINTN Segment;
+} EFI_PCI_IO_PRIVATE_DATA;
+
+#define EFI_PCI_IO_PRIVATE_DATA_SIGNATURE SIGNATURE_32('p', 'c', 'i', 'o')
+#define EFI_PCI_IO_PRIVATE_DATA_FROM_THIS(a) CR(a, EFI_PCI_IO_PRIVATE_DATA, PciIoProtocol, EFI_PCI_IO_PRIVATE_DATA_SIGNATURE)
+
+EFI_PCI_IO_DEVICE_PATH PciIoDevicePathTemplate =
+{
+ {
+ { ACPI_DEVICE_PATH, ACPI_DP, sizeof (ACPI_HID_DEVICE_PATH), 0},
+ EISA_PNP_ID(0x0A03), // HID
+ 0 // UID
+ },
+ {
+ { HARDWARE_DEVICE_PATH, HW_PCI_DP, sizeof (PCI_DEVICE_PATH), 0},
+ 0,
+ 0
+ },
+ { END_DEVICE_PATH_TYPE, END_ENTIRE_DEVICE_PATH_SUBTYPE, sizeof (EFI_DEVICE_PATH_PROTOCOL), 0}
+};
+
+STATIC
+VOID
+ConfigureUSBHost (
+ VOID
+ )
+{
+ EFI_STATUS Status;
+ UINT8 Data = 0;
+
+ // Take USB host out of force-standby mode
+ MmioWrite32(UHH_SYSCONFIG, UHH_SYSCONFIG_MIDLEMODE_NO_STANDBY
+ | UHH_SYSCONFIG_CLOCKACTIVITY_ON
+ | UHH_SYSCONFIG_SIDLEMODE_NO_STANDBY
+ | UHH_SYSCONFIG_ENAWAKEUP_ENABLE
+ | UHH_SYSCONFIG_AUTOIDLE_ALWAYS_RUN);
+ MmioWrite32(UHH_HOSTCONFIG, UHH_HOSTCONFIG_P3_CONNECT_STATUS_DISCONNECT
+ | UHH_HOSTCONFIG_P2_CONNECT_STATUS_DISCONNECT
+ | UHH_HOSTCONFIG_P1_CONNECT_STATUS_DISCONNECT
+ | UHH_HOSTCONFIG_ENA_INCR_ALIGN_DISABLE
+ | UHH_HOSTCONFIG_ENA_INCR16_ENABLE
+ | UHH_HOSTCONFIG_ENA_INCR8_ENABLE
+ | UHH_HOSTCONFIG_ENA_INCR4_ENABLE
+ | UHH_HOSTCONFIG_AUTOPPD_ON_OVERCUR_EN_ON
+ | UHH_HOSTCONFIG_P1_ULPI_BYPASS_ULPI_MODE);
+
+ // USB reset (GPIO 147 - Port 5 pin 19) output high
+ MmioAnd32(GPIO5_BASE + GPIO_OE, ~BIT19);
+ MmioWrite32(GPIO5_BASE + GPIO_SETDATAOUT, BIT19);
+
+ // Get the Power IC protocol.
+ Status = gBS->LocateProtocol(&gEmbeddedExternalDeviceProtocolGuid, NULL, (VOID **)&gTPS65950);
+ ASSERT_EFI_ERROR(Status);
+
+ //Enable power to the USB host.
+ Status = gTPS65950->Read(gTPS65950, EXTERNAL_DEVICE_REGISTER(I2C_ADDR_GRP_ID3, LEDEN), 1, &Data);
+ ASSERT_EFI_ERROR(Status);
+
+ //LEDAON & LEDAPWM control the power to the USB host so enable those bits.
+ Data |= (LEDAON | LEDAPWM);
+
+ Status = gTPS65950->Write(gTPS65950, EXTERNAL_DEVICE_REGISTER(I2C_ADDR_GRP_ID3, LEDEN), 1, &Data);
+ ASSERT_EFI_ERROR(Status);
+}
+
+EFI_STATUS
+PciIoPollMem (
+ IN EFI_PCI_IO_PROTOCOL *This,
+ IN EFI_PCI_IO_PROTOCOL_WIDTH Width,
+ IN UINT8 BarIndex,
+ IN UINT64 Offset,
+ IN UINT64 Mask,
+ IN UINT64 Value,
+ IN UINT64 Delay,
+ OUT UINT64 *Result
+ )
+{
+ ASSERT (FALSE);
+ return EFI_UNSUPPORTED;
+}
+
+EFI_STATUS
+PciIoPollIo (
+ IN EFI_PCI_IO_PROTOCOL *This,
+ IN EFI_PCI_IO_PROTOCOL_WIDTH Width,
+ IN UINT8 BarIndex,
+ IN UINT64 Offset,
+ IN UINT64 Mask,
+ IN UINT64 Value,
+ IN UINT64 Delay,
+ OUT UINT64 *Result
+ )
+{
+ ASSERT (FALSE);
+ return EFI_UNSUPPORTED;
+}
+
+EFI_STATUS
+PciIoMemRead (
+ IN EFI_PCI_IO_PROTOCOL *This,
+ IN EFI_PCI_IO_PROTOCOL_WIDTH Width,
+ IN UINT8 BarIndex,
+ IN UINT64 Offset,
+ IN UINTN Count,
+ IN OUT VOID *Buffer
+ )
+{
+ EFI_PCI_IO_PRIVATE_DATA *Private = EFI_PCI_IO_PRIVATE_DATA_FROM_THIS(This);
+
+ return PciRootBridgeIoMemRead (&Private->RootBridge.Io,
+ (EFI_PCI_ROOT_BRIDGE_IO_PROTOCOL_WIDTH) Width,
+ Private->ConfigSpace->Device.Bar[BarIndex] + Offset,
+ Count,
+ Buffer
+ );
+}
+
+EFI_STATUS
+PciIoMemWrite (
+ IN EFI_PCI_IO_PROTOCOL *This,
+ IN EFI_PCI_IO_PROTOCOL_WIDTH Width,
+ IN UINT8 BarIndex,
+ IN UINT64 Offset,
+ IN UINTN Count,
+ IN OUT VOID *Buffer
+ )
+{
+ EFI_PCI_IO_PRIVATE_DATA *Private = EFI_PCI_IO_PRIVATE_DATA_FROM_THIS(This);
+
+ return PciRootBridgeIoMemWrite (&Private->RootBridge.Io,
+ (EFI_PCI_ROOT_BRIDGE_IO_PROTOCOL_WIDTH) Width,
+ Private->ConfigSpace->Device.Bar[BarIndex] + Offset,
+ Count,
+ Buffer
+ );
+}
+
+EFI_STATUS
+PciIoIoRead (
+ IN EFI_PCI_IO_PROTOCOL *This,
+ IN EFI_PCI_IO_PROTOCOL_WIDTH Width,
+ IN UINT8 BarIndex,
+ IN UINT64 Offset,
+ IN UINTN Count,
+ IN OUT VOID *Buffer
+ )
+{
+ ASSERT (FALSE);
+ return EFI_UNSUPPORTED;
+}
+
+EFI_STATUS
+PciIoIoWrite (
+ IN EFI_PCI_IO_PROTOCOL *This,
+ IN EFI_PCI_IO_PROTOCOL_WIDTH Width,
+ IN UINT8 BarIndex,
+ IN UINT64 Offset,
+ IN UINTN Count,
+ IN OUT VOID *Buffer
+ )
+{
+ ASSERT (FALSE);
+ return EFI_UNSUPPORTED;
+}
+
+EFI_STATUS
+PciIoPciRead (
+ IN EFI_PCI_IO_PROTOCOL *This,
+ IN EFI_PCI_IO_PROTOCOL_WIDTH Width,
+ IN UINT32 Offset,
+ IN UINTN Count,
+ IN OUT VOID *Buffer
+ )
+{
+ EFI_PCI_IO_PRIVATE_DATA *Private = EFI_PCI_IO_PRIVATE_DATA_FROM_THIS(This);
+
+ return PciRootBridgeIoMemRW ((EFI_PCI_ROOT_BRIDGE_IO_PROTOCOL_WIDTH)Width,
+ Count,
+ TRUE,
+ (PTR)(UINTN)Buffer,
+ TRUE,
+ (PTR)(UINTN)(((UINT8 *)Private->ConfigSpace) + Offset)
+ );
+}
+
+EFI_STATUS
+PciIoPciWrite (
+ IN EFI_PCI_IO_PROTOCOL *This,
+ IN EFI_PCI_IO_PROTOCOL_WIDTH Width,
+ IN UINT32 Offset,
+ IN UINTN Count,
+ IN OUT VOID *Buffer
+ )
+{
+ EFI_PCI_IO_PRIVATE_DATA *Private = EFI_PCI_IO_PRIVATE_DATA_FROM_THIS(This);
+
+ return PciRootBridgeIoMemRW ((EFI_PCI_ROOT_BRIDGE_IO_PROTOCOL_WIDTH) Width,
+ Count,
+ TRUE,
+ (PTR)(UINTN)(((UINT8 *)Private->ConfigSpace) + Offset),
+ TRUE,
+ (PTR)(UINTN)Buffer
+ );
+}
+
+EFI_STATUS
+PciIoCopyMem (
+ IN EFI_PCI_IO_PROTOCOL *This,
+ IN EFI_PCI_IO_PROTOCOL_WIDTH Width,
+ IN UINT8 DestBarIndex,
+ IN UINT64 DestOffset,
+ IN UINT8 SrcBarIndex,
+ IN UINT64 SrcOffset,
+ IN UINTN Count
+ )
+{
+ ASSERT (FALSE);
+ return EFI_UNSUPPORTED;
+}
+
+EFI_STATUS
+PciIoMap (
+ IN EFI_PCI_IO_PROTOCOL *This,
+ IN EFI_PCI_IO_PROTOCOL_OPERATION Operation,
+ IN VOID *HostAddress,
+ IN OUT UINTN *NumberOfBytes,
+ OUT EFI_PHYSICAL_ADDRESS *DeviceAddress,
+ OUT VOID **Mapping
+ )
+{
+ MAP_INFO_INSTANCE *Map;
+ EFI_STATUS Status;
+
+ if ( HostAddress == NULL || NumberOfBytes == NULL ||
+ DeviceAddress == NULL || Mapping == NULL ) {
+
+ return EFI_INVALID_PARAMETER;
+ }
+
+
+ if (Operation >= EfiPciOperationMaximum) {
+ return EFI_INVALID_PARAMETER;
+ }
+
+ *DeviceAddress = ConvertToPhysicalAddress (HostAddress);
+
+ // Data cache flush (HostAddress, NumberOfBytes);
+
+ // Remember range so we can flush on the other side
+ Status = gBS->AllocatePool (EfiBootServicesData, sizeof (PCI_DMA_MAP), (VOID **) &Map);
+ if (EFI_ERROR(Status)) {
+ return EFI_OUT_OF_RESOURCES;
+ }
+
+ *Mapping = Map;
+
+ Map->HostAddress = (UINTN)HostAddress;
+ Map->DeviceAddress = *DeviceAddress;
+ Map->NumberOfBytes = *NumberOfBytes;
+ Map->Operation = Operation;
+
+ // EfiCpuFlushTypeWriteBack, EfiCpuFlushTypeInvalidate
+ gCpu->FlushDataCache (gCpu, (EFI_PHYSICAL_ADDRESS)(UINTN)HostAddress, *NumberOfBytes, EfiCpuFlushTypeWriteBackInvalidate);
+
+ return EFI_SUCCESS;
+}
+
+EFI_STATUS
+PciIoUnmap (
+ IN EFI_PCI_IO_PROTOCOL *This,
+ IN VOID *Mapping
+ )
+{
+ PCI_DMA_MAP *Map;
+
+ if (Mapping == NULL) {
+ ASSERT (FALSE);
+ return EFI_INVALID_PARAMETER;
+ }
+
+ Map = (PCI_DMA_MAP *)Mapping;
+ if (Map->Operation == EfiPciOperationBusMasterWrite) {
+ //
+ // Make sure we read buffer from uncached memory and not the cache
+ //
+ gCpu->FlushDataCache (gCpu, Map->HostAddress, Map->NumberOfBytes, EfiCpuFlushTypeInvalidate);
+ }
+
+ FreePool (Map);
+
+ return EFI_SUCCESS;
+}
+
+EFI_STATUS
+PciIoAllocateBuffer (
+ IN EFI_PCI_IO_PROTOCOL *This,
+ IN EFI_ALLOCATE_TYPE Type,
+ IN EFI_MEMORY_TYPE MemoryType,
+ IN UINTN Pages,
+ OUT VOID **HostAddress,
+ IN UINT64 Attributes
+ )
+{
+ if (Attributes & EFI_PCI_ATTRIBUTE_INVALID_FOR_ALLOCATE_BUFFER) {
+ return EFI_UNSUPPORTED;
+ }
+
+ if (HostAddress == NULL) {
+ return EFI_INVALID_PARAMETER;
+ }
+
+ //
+ // The only valid memory types are EfiBootServicesData and EfiRuntimeServicesData
+ //
+ // We used uncached memory to keep coherency
+ //
+ if (MemoryType == EfiBootServicesData) {
+ *HostAddress = UncachedAllocatePages (Pages);
+ } else if (MemoryType != EfiRuntimeServicesData) {
+ *HostAddress = UncachedAllocateRuntimePages (Pages);
+ } else {
+ return EFI_INVALID_PARAMETER;
+ }
+
+ return EFI_SUCCESS;
+}
+
+EFI_STATUS
+PciIoFreeBuffer (
+ IN EFI_PCI_IO_PROTOCOL *This,
+ IN UINTN Pages,
+ IN VOID *HostAddress
+ )
+{
+ if (HostAddress == NULL) {
+ return EFI_INVALID_PARAMETER;
+ }
+
+ UncachedFreePages (HostAddress, Pages);
+ return EFI_SUCCESS;
+}
+
+
+EFI_STATUS
+PciIoFlush (
+ IN EFI_PCI_IO_PROTOCOL *This
+ )
+{
+ return EFI_SUCCESS;
+}
+
+EFI_STATUS
+PciIoGetLocation (
+ IN EFI_PCI_IO_PROTOCOL *This,
+ OUT UINTN *SegmentNumber,
+ OUT UINTN *BusNumber,
+ OUT UINTN *DeviceNumber,
+ OUT UINTN *FunctionNumber
+ )
+{
+ EFI_PCI_IO_PRIVATE_DATA *Private = EFI_PCI_IO_PRIVATE_DATA_FROM_THIS(This);
+
+ if (SegmentNumber != NULL) {
+ *SegmentNumber = Private->Segment;
+ }
+
+ if (BusNumber != NULL) {
+ *BusNumber = 0xff;
+ }
+
+ if (DeviceNumber != NULL) {
+ *DeviceNumber = 0;
+ }
+
+ if (FunctionNumber != NULL) {
+ *FunctionNumber = 0;
+ }
+
+ return EFI_SUCCESS;
+}
+
+EFI_STATUS
+PciIoAttributes (
+ IN EFI_PCI_IO_PROTOCOL *This,
+ IN EFI_PCI_IO_PROTOCOL_ATTRIBUTE_OPERATION Operation,
+ IN UINT64 Attributes,
+ OUT UINT64 *Result OPTIONAL
+ )
+{
+ switch (Operation) {
+ case EfiPciIoAttributeOperationGet:
+ case EfiPciIoAttributeOperationSupported:
+ if (Result == NULL) {
+ return EFI_INVALID_PARAMETER;
+ }
+ // We are not a real PCI device so just say things we kind of do
+ *Result = EFI_PCI_IO_ATTRIBUTE_MEMORY | EFI_PCI_IO_ATTRIBUTE_BUS_MASTER | EFI_PCI_DEVICE_ENABLE;
+ break;
+
+ case EfiPciIoAttributeOperationSet:
+ case EfiPciIoAttributeOperationEnable:
+ case EfiPciIoAttributeOperationDisable:
+ // Since we are not a real PCI device no enable/set or disable operations exist.
+ return EFI_SUCCESS;
+
+ default:
+ ASSERT (FALSE);
+ return EFI_INVALID_PARAMETER;
+ };
+ return EFI_SUCCESS;
+}
+
+EFI_STATUS
+PciIoGetBarAttributes (
+ IN EFI_PCI_IO_PROTOCOL *This,
+ IN UINT8 BarIndex,
+ OUT UINT64 *Supports, OPTIONAL
+ OUT VOID **Resources OPTIONAL
+ )
+{
+ ASSERT (FALSE);
+ return EFI_UNSUPPORTED;
+}
+
+EFI_STATUS
+PciIoSetBarAttributes (
+ IN EFI_PCI_IO_PROTOCOL *This,
+ IN UINT64 Attributes,
+ IN UINT8 BarIndex,
+ IN OUT UINT64 *Offset,
+ IN OUT UINT64 *Length
+ )
+{
+ ASSERT (FALSE);
+ return EFI_UNSUPPORTED;
+}
+
+EFI_PCI_IO_PROTOCOL PciIoTemplate =
+{
+ PciIoPollMem,
+ PciIoPollIo,
+ PciIoMemRead,
+ PciIoMemWrite,
+ PciIoIoRead,
+ PciIoIoWrite,
+ PciIoPciRead,
+ PciIoPciWrite,
+ PciIoCopyMem,
+ PciIoMap,
+ PciIoUnmap,
+ PciIoAllocateBuffer,
+ PciIoFreeBuffer,
+ PciIoFlush,
+ PciIoGetLocation,
+ PciIoAttributes,
+ PciIoGetBarAttributes,
+ PciIoSetBarAttributes,
+ 0,
+ 0
+};
+
+EFI_STATUS
+EFIAPI
+PciEmulationEntryPoint (
+ IN EFI_HANDLE ImageHandle,
+ IN EFI_SYSTEM_TABLE *SystemTable
+ )
+{
+ EFI_STATUS Status;
+ EFI_HANDLE Handle;
+ EFI_PCI_IO_PRIVATE_DATA *Private;
+ UINT8 CapabilityLength;
+ UINT8 PhysicalPorts;
+ UINTN Count;
+
+ // Get the Cpu protocol for later use
+ Status = gBS->LocateProtocol(&gEfiCpuArchProtocolGuid, NULL, (VOID **)&gCpu);
+ ASSERT_EFI_ERROR(Status);
+
+ //Configure USB host for OMAP3530.
+ ConfigureUSBHost();
+
+ // Create a private structure
+ Private = AllocatePool(sizeof(EFI_PCI_IO_PRIVATE_DATA));
+ if (Private == NULL) {
+ Status = EFI_OUT_OF_RESOURCES;
+ return Status;
+ }
+
+ Private->Signature = EFI_PCI_IO_PRIVATE_DATA_SIGNATURE; // Fill in signature
+ Private->RootBridge.Signature = PCI_ROOT_BRIDGE_SIGNATURE; // Fake Root Bridge structure needs a signature too
+ Private->RootBridge.MemoryStart = USB_EHCI_HCCAPBASE; // Get the USB capability register base
+ Private->Segment = 0; // Default to segment zero
+
+ // Find out the capability register length and number of physical ports.
+ CapabilityLength = MmioRead8(Private->RootBridge.MemoryStart);
+ PhysicalPorts = (MmioRead32(Private->RootBridge.MemoryStart + 0x4)) & 0x0000000F;
+
+ // Calculate the total size of the USB registers.
+ Private->RootBridge.MemorySize = CapabilityLength + (HOST_CONTROLLER_OPERATION_REG_SIZE + ((4 * PhysicalPorts) - 1));
+
+ // Enable Port Power bit in Port status and control registers in EHCI register space.
+ // Port Power Control (PPC) bit in the HCSPARAMS register is already set which indicates
+ // host controller implementation includes port power control.
+ for (Count = 0; Count < PhysicalPorts; Count++) {
+ MmioOr32((Private->RootBridge.MemoryStart + CapabilityLength + HOST_CONTROLLER_OPERATION_REG_SIZE + 4*Count), 0x00001000);
+ }
+
+ // Create fake PCI config space.
+ Private->ConfigSpace = AllocateZeroPool(sizeof(PCI_TYPE00));
+ if (Private->ConfigSpace == NULL) {
+ Status = EFI_OUT_OF_RESOURCES;
+ FreePool(Private);
+ return Status;
+ }
+
+ // Configure PCI config space
+ Private->ConfigSpace->Hdr.VendorId = 0x3530;
+ Private->ConfigSpace->Hdr.DeviceId = 0x3530;
+ Private->ConfigSpace->Hdr.ClassCode[0] = 0x20;
+ Private->ConfigSpace->Hdr.ClassCode[1] = 0x03;
+ Private->ConfigSpace->Hdr.ClassCode[2] = 0x0C;
+ Private->ConfigSpace->Device.Bar[0] = Private->RootBridge.MemoryStart;
+
+ Handle = NULL;
+
+ // Unique device path.
+ CopyMem(&Private->DevicePath, &PciIoDevicePathTemplate, sizeof(PciIoDevicePathTemplate));
+ Private->DevicePath.AcpiDevicePath.UID = 0;
+
+ // Copy protocol structure
+ CopyMem(&Private->PciIoProtocol, &PciIoTemplate, sizeof(PciIoTemplate));
+
+ Status = gBS->InstallMultipleProtocolInterfaces(&Handle,
+ &gEfiPciIoProtocolGuid, &Private->PciIoProtocol,
+ &gEfiDevicePathProtocolGuid, &Private->DevicePath,
+ NULL);
+ if (EFI_ERROR(Status)) {
+ DEBUG((EFI_D_ERROR, "PciEmulationEntryPoint InstallMultipleProtocolInterfaces() failed.\n"));
+ }
+
+ return Status;
+}
+