// /***************************************************************************
// The Disc Image Chef
// ----------------------------------------------------------------------------
//
// Filename       : ExtendedCSD.cs
// Author(s)      : Natalia Portillo <claunia@claunia.com>
//
// Component      : Component
//
// --[ Description ] ----------------------------------------------------------
//
//     Description
//
// --[ License ] --------------------------------------------------------------
//
//     This library is free software; you can redistribute it and/or modify
//     it under the terms of the GNU Lesser General Public License as
//     published by the Free Software Foundation; either version 2.1 of the
//     License, or (at your option) any later version.
//
//     This library is distributed in the hope that it will be useful, but
//     WITHOUT ANY WARRANTY; without even the implied warranty of
//     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
//     Lesser General Public License for more details.
//
//     You should have received a copy of the GNU Lesser General Public
//     License along with this library; if not, see <http://www.gnu.org/licenses/>.
//
// ----------------------------------------------------------------------------
// Copyright © 2011-2017 Natalia Portillo
// ****************************************************************************/

using System;
using System.Runtime.InteropServices;
using System.Text;

namespace DiscImageChef.Decoders.MMC
{
    [StructLayout(LayoutKind.Sequential)]
    public class ExtendedCSD
    {
        [MarshalAs(UnmanagedType.ByValArray, SizeConst = 6)]
        public byte[] Reserved0;
        public byte ExtendedSecurityCommandsError;
        public byte SupportedCommandSets;
        public byte HPIFeatures;
        public byte BackgroundOperationsSupport;
        public byte MaxPackedReadCommands;
        public byte MaxPackedWriteCommands;
        public byte DataTagSupport;
        public byte TagUnitSize;
        public byte TagResourcesSize;
        public byte ContextManagementCaps;
        public byte LargeUnitSize;
        public byte ExtendedPartitionsSupport;
        public byte SupportedModes;
        public byte FFUFeatures;
        public byte OperationCodesTimeout;
        public uint FFUArgument;
        public byte BarrierSupport;
        [MarshalAs(UnmanagedType.ByValArray, SizeConst = 177)]
        public byte[] Reserved1;
        public byte CMDQueuingSupport;
        public byte CMDQueuingDepth;
        public uint NumberofFWSectorsCorrectlyProgrammed;
        [MarshalAs(UnmanagedType.ByValArray, SizeConst = 32)]
        public byte[] VendorHealthReport;
        public byte DeviceLifeEstimationTypeB;
        public byte DeviceLifeEstimationTypeA;
        public byte PreEOLInformation;
        public byte OptimalReadSize;
        public byte OptimalWriteSize;
        public byte OptimalTrimUnitSize;
        public ushort DeviceVersion;
        public ulong FirmwareVersion;
        public byte PowerClassDDR200;
        public uint CacheSize;
        public byte GenericCMD6Timeout;
        public byte PowerOffNotificationTimeout;
        public byte BackgroundOperationsStatus;
        public uint CorrectlyProgrammedSectors;
        public byte InitializationTimeAfterPartition;
        public byte CacheFlushingPolicy;
        public byte PowerClassDDR52;
        public byte PowerClassDDR52_195;
        public byte PowerClassDDR200_195;
        public byte PowerClassDDR200_130;
        public byte MinimumWritePerformanceDDR52;
        public byte MinimumReadPerformanceDDR52;
        public byte Reserved2;
        public byte TRIMMultiplier;
        public byte SecureFeatureSupport;
        public byte SecureEraseMultiplier;
        public byte SecureTRIMMultiplier;
        public byte BootInformation;
        public byte Reserved3;
        public byte BootPartitionSize;
        public byte AccessSize;
        public byte HighCapacityEraseUnitSize;
        public byte HighCapacityEraseTimeout;
        public byte ReliableWriteSectorCount;
        public byte HighCapacityWriteProtectGroupSize;
        public byte SleepCurrentVcc;
        public byte SleepCurrentVccq;
        public byte ProductionStateAwarenessTimeout;
        public byte SleepAwakeTimeout;
        public byte SleepNotificationTimeout;
        public uint SectorCount;
        public byte SecureWriteProtectInformation;
        public byte MinimumWritePerformance52;
        public byte MinimumReadPerformance52;
        public byte MinimumWritePerformance26;
        public byte MinimumReadPerformance26;
        public byte MinimumWritePerformance26_4;
        public byte MinimumReadPerformance26_4;
        public byte Reserved4;
        public byte PowerClass26;
        public byte PowerClass52;
        public byte PowerClass26_195;
        public byte PowerClass52_195;
        public byte PartitionSwitchingTime;
        public byte OutOfInterruptBusyTiming;
        public byte DriverStrength;
        public byte DeviceType;
        public byte Reserved5;
        public byte Structure;
        public byte Reserved6;
        public byte Revision;
        public byte CommandSet;
        public byte Reserved7;
        public byte CommandSetRevision;
        public byte Reserved8;
        public byte PowerClass;
        public byte Reserved9;
        public byte HighSpeedInterfaceTiming;
        public byte StrobeSupport;
        public byte BusWidth;
        public byte Reserved10;
        public byte ErasedMemoryContent;
        public byte Reserved11;
        public byte PartitionConfiguration;
        public byte BootConfigProtection;
        public byte BootBusConditions;
        public byte Reserved12;
        public byte HighDensityEraseGroupDefinition;
        public byte BootWriteProtectionStatus;
        public byte BootAreaWriteProtectionRegister;
        public byte Reserved13;
        public byte UserAreaWriteProtectionRegister;
        public byte Reserved14;
        public byte FirmwareConfiguration;
        public byte RPMBSize;
        public byte WriteReliabilitySettingRegister;
        public byte WriteReliabilityParameterRegister;
        public byte StartSanitizeOperation;
        public byte ManuallyStartBackgroundOperations;
        public byte EnableBackgroundOperationsHandshake;
        public byte HWResetFunction;
        public byte HPIManagement;
        public byte PartitioningSupport;
        [MarshalAs(UnmanagedType.ByValArray, SizeConst = 3)]
        public byte[] MaxEnhancedAreaSize;
        public byte PartitionsAttribute;
        public byte PartitioningSetting;
        [MarshalAs(UnmanagedType.ByValArray, SizeConst = 12)]
        public byte[] GeneralPurposePartitionSize;
        [MarshalAs(UnmanagedType.ByValArray, SizeConst = 3)]
        public byte[] EnhancedUserDataAreaSize;
        public uint EnhancedUserDataStartAddress;
        public byte Reserved15;
        public byte BadBlockManagementMode;
        public byte ProductionStateAwareness;
        public byte PackageCaseTemperatureControl;
        public byte PeriodicWakeUp;
        public byte SupportsProgramCxDInDDR;
        public ushort Reserved16;
        [MarshalAs(UnmanagedType.ByValArray, SizeConst = 64)]
        public byte[] VendorSpecific;
        public byte NativeSectorSize;
        public byte SectorSizeEmulation;
        public byte SectorSize;
        public byte InitializationTimeout;
        public byte Class6CommandsControl;
        public byte AddressedGroupToBeReleased;
        public ushort ExceptionEventsControl;
        public ushort ExceptionEventsStatus;
        public ushort ExtendedPartitionsAttribute;
        [MarshalAs(UnmanagedType.ByValArray, SizeConst = 15)]
        public byte[] ContextConfiguration;
        public byte PackedCommandStatus;
        public byte PackedCommandFailureIndex;
        public byte PowerOffNotification;
        public byte CacheControl;
        public byte CacheFlushing;
        public byte BarrierControl;
        public byte ModeConfig;
        public byte ModeOperationCodes;
        public ushort Reserved17;
        public byte FFUStatus;
        public uint PreLoadingDataSize;
        public uint MaxPreLoadingDataSize;
        public byte ProductStateAwarenessEnablement;
        public byte SecureRemovalType;
        public byte CommandQueueModeEnable;
        [MarshalAs(UnmanagedType.ByValArray, SizeConst = 15)]
        public byte[] Reserved18;
    }

    public partial class Decoders
    {
        public static ExtendedCSD DecodeExtendedCSD(byte[] response)
        {
            if(response == null)
                return null;

            if(response.Length != 512)
                return null;

            ExtendedCSD csd = new ExtendedCSD();
            GCHandle handle = GCHandle.Alloc(response, GCHandleType.Pinned);
            csd = (ExtendedCSD)Marshal.PtrToStructure(handle.AddrOfPinnedObject(), typeof(ExtendedCSD));
            handle.Free();

            return csd;
        }

        public static string PrettifyExtendedCSD(ExtendedCSD csd)
        {
            if(csd == null)
                return null;

            StringBuilder sb = new StringBuilder();
            sb.AppendLine("MultiMediaCard Extended Device Specific Data Register:");

            double unit;

            if((csd.HPIFeatures & 0x01) == 0x01)
            {
                if((csd.HPIFeatures & 0x02) == 0x02)
                    sb.AppendLine("\tDevice implements HPI using CMD12");
                else
                    sb.AppendLine("\tDevice implements HPI using CMD13");
            }

            if((csd.BackgroundOperationsSupport & 0x01) == 0x01)
                sb.AppendLine("\tDevice supports background operations");

            sb.AppendFormat("\tDevice supports a maximum of {0} packed reads and {1} packed writes", csd.MaxPackedReadCommands, csd.MaxPackedWriteCommands).AppendLine();

            if((csd.DataTagSupport & 0x01) == 0x01)
            {
                sb.AppendLine("\tDevice supports Data Tag");
                sb.AppendFormat("\tTags must be in units of {0} sectors", Math.Pow(2, csd.TagUnitSize)).AppendLine();
            }

            if((csd.ExtendedPartitionsSupport & 0x01) == 0x01)
                sb.AppendLine("\tDevice supports non-persistent extended partitions");
            if((csd.ExtendedPartitionsSupport & 0x02) == 0x02)
                sb.AppendLine("\tDevice supports system code extended partitions");

            if((csd.SupportedModes & 0x01) == 0x01)
                sb.AppendLine("\tDevice supports FFU");
            if((csd.SupportedModes & 0x02) == 0x02)
                sb.AppendLine("\tDevice supports Vendor Specific Mode");

            if((csd.CMDQueuingSupport & 0x01) == 0x01)
                sb.AppendFormat("\tDevice supports command queuing with a depth of {0}", csd.CMDQueuingDepth + 1).AppendLine();

            sb.AppendFormat("\t{0} firmware sectors correctly programmed", csd.NumberofFWSectorsCorrectlyProgrammed).AppendLine();

            switch(csd.DeviceLifeEstimationTypeB)
            {
                case 1:
                    sb.AppendLine("\tDevice used between 0% and 10% of its estimated life time");
                    break;
                case 2:
                    sb.AppendLine("\tDevice used between 10% and 20% of its estimated life time");
                    break;
                case 3:
                    sb.AppendLine("\tDevice used between 20% and 30% of its estimated life time");
                    break;
                case 4:
                    sb.AppendLine("\tDevice used between 30% and 40% of its estimated life time");
                    break;
                case 5:
                    sb.AppendLine("\tDevice used between 40% and 50% of its estimated life time");
                    break;
                case 6:
                    sb.AppendLine("\tDevice used between 50% and 60% of its estimated life time");
                    break;
                case 7:
                    sb.AppendLine("\tDevice used between 60% and 70% of its estimated life time");
                    break;
                case 8:
                    sb.AppendLine("\tDevice used between 70% and 80% of its estimated life time");
                    break;
                case 9:
                    sb.AppendLine("\tDevice used between 80% and 90% of its estimated life time");
                    break;
                case 10:
                    sb.AppendLine("\tDevice used between 90% and 100% of its estimated life time");
                    break;
                case 11:
                    sb.AppendLine("\tDevice exceeded its maximum estimated life time");
                    break;
            }

            switch(csd.DeviceLifeEstimationTypeA)
            {
                case 1:
                    sb.AppendLine("\tDevice used between 0% and 10% of its estimated life time");
                    break;
                case 2:
                    sb.AppendLine("\tDevice used between 10% and 20% of its estimated life time");
                    break;
                case 3:
                    sb.AppendLine("\tDevice used between 20% and 30% of its estimated life time");
                    break;
                case 4:
                    sb.AppendLine("\tDevice used between 30% and 40% of its estimated life time");
                    break;
                case 5:
                    sb.AppendLine("\tDevice used between 40% and 50% of its estimated life time");
                    break;
                case 6:
                    sb.AppendLine("\tDevice used between 50% and 60% of its estimated life time");
                    break;
                case 7:
                    sb.AppendLine("\tDevice used between 60% and 70% of its estimated life time");
                    break;
                case 8:
                    sb.AppendLine("\tDevice used between 70% and 80% of its estimated life time");
                    break;
                case 9:
                    sb.AppendLine("\tDevice used between 80% and 90% of its estimated life time");
                    break;
                case 10:
                    sb.AppendLine("\tDevice used between 90% and 100% of its estimated life time");
                    break;
                case 11:
                    sb.AppendLine("\tDevice exceeded its maximum estimated life time");
                    break;
            }

            switch(csd.PreEOLInformation)
            {
                case 1:
                    sb.AppendLine("\tDevice informs it's in good health");
                    break;
                case 2:
                    sb.AppendLine("\tDevice informs it should be replaced soon");
                    break;
                case 3:
                    sb.AppendLine("\tDevice informs it should be replace immediately");
                    break;
            }

            sb.AppendFormat("\tOptimal read size is {0} logical sectors", csd.OptimalReadSize).AppendLine();
            sb.AppendFormat("\tOptimal write size is {0} logical sectors", csd.OptimalWriteSize).AppendLine();
            sb.AppendFormat("\tOptimal trim size is {0} logical sectors", csd.OptimalTrimUnitSize).AppendLine();

            sb.AppendFormat("\tDevice version: {0}", csd.DeviceVersion).AppendLine();
            sb.AppendFormat("\tFirmware version: {0}", csd.FirmwareVersion).AppendLine();

            if(csd.CacheSize == 0)
                sb.AppendLine("\tDevice has no cache");
            else
                sb.AppendFormat("\tDevice has {0} KiB of cache", csd.CacheSize).AppendLine();

            if(csd.GenericCMD6Timeout > 0)
                sb.AppendFormat("\tDevice takes a maximum of {0} ms by default for a SWITCH command", csd.GenericCMD6Timeout * 10).AppendLine();

            if(csd.PowerOffNotificationTimeout > 0)
                sb.AppendFormat("\tDevice takes a maximum of {0} by default to power off from a SWITCH command notification", csd.PowerOffNotificationTimeout * 10).AppendLine();

            switch(csd.BackgroundOperationsStatus & 0x03)
            {
                case 0:
                    sb.AppendLine("\tDevice has no pending background operations");
                    break;
                case 1:
                    sb.AppendLine("\tDevice has non critical operations outstanding");
                    break;
                case 2:
                    sb.AppendLine("\tDevice has performance impacted operations outstanding");
                    break;
                case 3:
                    sb.AppendLine("\tDevice has critical operations outstanding");
                    break;
            }

            sb.AppendFormat("\tLast WRITE MULTIPLE command correctly programmed {0} sectors", csd.CorrectlyProgrammedSectors).AppendLine();

            if(csd.InitializationTimeAfterPartition > 0)
                sb.AppendFormat("\tDevice takes a maximum of {0} ms for initialization after partition", csd.InitializationTimeAfterPartition * 100).AppendLine();

            if((csd.CacheFlushingPolicy & 0x01) == 0x01)
                sb.AppendLine("\tDevice uses a FIFO policy for cache flushing");

            if(csd.TRIMMultiplier > 0)
                sb.AppendFormat("\tDevice takes a maximum of {0} ms for trimming a single erase group", csd.TRIMMultiplier * 300).AppendLine();

            if((csd.SecureFeatureSupport & 0x40) == 0x40)
                sb.AppendLine("\tDevice supports the sanitize operation");
            if((csd.SecureFeatureSupport & 0x10) == 0x10)
                sb.AppendLine("\tDevice supports supports the secure and insecure trim operations");
            if((csd.SecureFeatureSupport & 0x04) == 0x04)
                sb.AppendLine("\tDevice supports automatic erase on retired defective blocks");
            if((csd.SecureFeatureSupport & 0x01) == 0x01)
                sb.AppendLine("\tDevice supports secure purge operations");

            if(csd.SecureEraseMultiplier > 0)
                sb.AppendFormat("\tDevice takes a maximum of {0} ms for securely erasing a single erase group", csd.SecureEraseMultiplier * 300).AppendLine();
            if(csd.SecureTRIMMultiplier > 0)
                sb.AppendFormat("\tDevice takes a maximum of {0} ms for securely trimming a single erase group", csd.SecureTRIMMultiplier * 300).AppendLine();

            if((csd.BootInformation & 0x04) == 0x04)
                sb.AppendLine("\tDevice supports high speed timing on boot");
            if((csd.BootInformation & 0x02) == 0x02)
                sb.AppendLine("\tDevice supports dual data rate on boot");
            if((csd.BootInformation & 0x01) == 0x01)
                sb.AppendLine("\tDevice supports alternative boot method");

            if(csd.BootPartitionSize > 0)
                sb.AppendFormat("\tDevice has a {0} KiB boot partition", csd.BootPartitionSize * 128).AppendLine();

            if((csd.AccessSize & 0x0F) > 0)
                sb.AppendFormat("\tDevice has a page size of {0} KiB", (csd.AccessSize & 0x0F) * 512.0 / 1024.0).AppendLine();

            if(csd.HighCapacityEraseUnitSize > 0)
                sb.AppendFormat("\tDevice erase groups are {0} KiB", csd.HighCapacityEraseUnitSize * 512).AppendLine();


            if(csd.HighCapacityEraseTimeout > 0)
                sb.AppendFormat("\tDevice takes a maximum of {0} ms for erasing a single erase group", csd.HighCapacityEraseTimeout * 300).AppendLine();

            if(csd.HighCapacityWriteProtectGroupSize > 0)
                sb.AppendFormat("\tDevice smallest write protect group is made of {0} erase groups", csd.HighCapacityWriteProtectGroupSize).AppendLine();

            if(csd.SleepCurrentVcc > 0)
            {
                unit = Math.Pow(2, csd.SleepCurrentVcc);
                if(unit > 1000)
                    sb.AppendFormat("\tDevice uses {0} mA on Vcc when sleeping", unit / 1000).AppendLine();
                else
                    sb.AppendFormat("\tDevice uses {0} μA on Vcc when sleeping", unit).AppendLine();
            }

            if(csd.SleepCurrentVccq > 0)
            {
                unit = Math.Pow(2, csd.SleepCurrentVccq);
                if(unit > 1000)
                    sb.AppendFormat("\tDevice uses {0} mA on Vccq when sleeping", unit / 1000).AppendLine();
                else
                    sb.AppendFormat("\tDevice uses {0} μA on Vccq when sleeping", unit).AppendLine();
            }

            if(csd.ProductionStateAwarenessTimeout > 0)
            {
                unit = Math.Pow(2, csd.ProductionStateAwareness) * 100;
                if(unit > 1000000)
                    sb.AppendFormat("\tDevice takes a maximum of {0} s to switch production state awareness", unit / 1000000).AppendLine();
                else if(unit > 1000)
                    sb.AppendFormat("\tDevice takes a maximum of {0} ms to switch production state awareness", unit / 1000).AppendLine();
                else
                    sb.AppendFormat("\tDevice takes a maximum of {0} μs to switch production state awareness", unit).AppendLine();
            }

            if(csd.SleepAwakeTimeout > 0)
            {
                unit = Math.Pow(2, csd.SleepAwakeTimeout) * 100;
                if(unit > 1000000)
                    sb.AppendFormat("\tDevice takes a maximum of {0} ms to transition between sleep and standby states", unit / 1000000).AppendLine();
                else if(unit > 1000)
                    sb.AppendFormat("\tDevice takes a maximum of {0} μs to transition between sleep and standby states", unit / 1000).AppendLine();
                else
                    sb.AppendFormat("\tDevice takes a maximum of {0} ns to transition between sleep and standby states", unit).AppendLine();
            }

            if(csd.SleepNotificationTimeout > 0)
            {
                unit = Math.Pow(2, csd.SleepNotificationTimeout) * 10;
                if(unit > 1000000)
                    sb.AppendFormat("\tDevice takes a maximum of {0} s to move to sleep state", unit / 1000000).AppendLine();
                else if(unit > 1000)
                    sb.AppendFormat("\tDevice takes a maximum of {0} ms to move to sleep state", unit / 1000).AppendLine();
                else
                    sb.AppendFormat("\tDevice takes a maximum of {0} μs to move to sleep state", unit).AppendLine();
            }

            sb.AppendFormat("\tDevice has {0} sectors", csd.SectorCount).AppendLine();

            if((csd.SecureWriteProtectInformation & 0x01) == 0x01)
            {
                sb.AppendLine("\tDevice supports secure write protection");
                if((csd.SecureWriteProtectInformation & 0x02) == 0x02)
                    sb.AppendLine("\tDevice has secure write protection enabled");
            }

            unit = csd.MinimumReadPerformance26 * 150;
            if(csd.MinimumReadPerformance26 == 0)
                sb.AppendLine("\tDevice cannot achieve 2.4MB/s reading in SDR 26Mhz mode");
            else
                sb.AppendFormat("\tDevice can achieve a minimum of {0}MB/s reading in SDR 26Mhz mode", unit / 1000).AppendLine();

            unit = csd.MinimumReadPerformance26_4 * 150;
            if(csd.MinimumReadPerformance26_4 == 0)
                sb.AppendLine("\tDevice cannot achieve 2.4MB/s reading in SDR 26Mhz 4-bit mode");
            else
                sb.AppendFormat("\tDevice can achieve a minimum of {0}MB/s reading in SDR 26Mhz 4-bit mode", unit / 1000).AppendLine();

            unit = csd.MinimumReadPerformance52 * 150;
            if(csd.MinimumReadPerformance52 == 0)
                sb.AppendLine("\tDevice cannot achieve 2.4MB/s reading in SDR 52Mhz mode");
            else
                sb.AppendFormat("\tDevice can achieve a minimum of {0}MB/s reading in SDR 52Mhz mode", unit / 1000).AppendLine();

            unit = csd.MinimumReadPerformanceDDR52 * 300;
            if(csd.MinimumReadPerformanceDDR52 == 0)
                sb.AppendLine("\tDevice cannot achieve 4.8MB/s reading in DDR 52Mhz mode");
            else
                sb.AppendFormat("\tDevice can achieve a minimum of {0}MB/s reading in DDR 52Mhz mode", unit / 1000).AppendLine();

            unit = csd.MinimumWritePerformance26 * 150;
            if(csd.MinimumWritePerformance26 == 0)
                sb.AppendLine("\tDevice cannot achieve 2.4MB/s writing in SDR 26Mhz mode");
            else
                sb.AppendFormat("\tDevice can achieve a minimum of {0}MB/s writing in SDR 26Mhz mode", unit / 1000).AppendLine();

            unit = csd.MinimumWritePerformance26_4 * 150;
            if(csd.MinimumWritePerformance26_4 == 0)
                sb.AppendLine("\tDevice cannot achieve 2.4MB/s writing in SDR 26Mhz 4-bit mode");
            else
                sb.AppendFormat("\tDevice can achieve a minimum of {0}MB/s writing in SDR 26Mhz 4-bit mode", unit / 1000).AppendLine();

            unit = csd.MinimumWritePerformance52 * 150;
            if(csd.MinimumWritePerformance52 == 0)
                sb.AppendLine("\tDevice cannot achieve 2.4MB/s writing in SDR 52Mhz mode");
            else
                sb.AppendFormat("\tDevice can achieve a minimum of {0}MB/s writing in SDR 52Mhz mode", unit / 1000).AppendLine();

            unit = csd.MinimumWritePerformanceDDR52 * 300;
            if(csd.MinimumWritePerformanceDDR52 == 0)
                sb.AppendLine("\tDevice cannot achieve 4.8MB/s writing in DDR 52Mhz mode");
            else
                sb.AppendFormat("\tDevice can achieve a minimum of {0}MB/s writing in DDR 52Mhz mode", unit / 1000).AppendLine();

            if(csd.PartitionSwitchingTime > 0)
                sb.AppendFormat("\tDevice can take a maximum of {0} ms when switching partitions", csd.PartitionSwitchingTime * 10).AppendLine();

            if(csd.OutOfInterruptBusyTiming > 0)
                sb.AppendFormat("\tDevice can take a maximum of {0} ms when releasing from an interrupt", csd.OutOfInterruptBusyTiming * 10).AppendLine();

            if((csd.DeviceType & 0x01) == 0x01)
                sb.AppendLine("\tDevice supports 26 Mhz mode");
            if((csd.DeviceType & 0x02) == 0x02)
                sb.AppendLine("\tDevice supports 52 Mhz mode");
            if((csd.DeviceType & 0x04) == 0x04)
                sb.AppendLine("\tDevice supports DDR 52 Mhz mode at 1.8V or 3V");
            if((csd.DeviceType & 0x08) == 0x08)
                sb.AppendLine("\tDevice supports DDR 52 Mhz mode 1.2V");
            if((csd.DeviceType & 0x10) == 0x10)
                sb.AppendLine("\tDevice supports HS-200 mode (SDR 200Mhz) at 1.8V");
            if((csd.DeviceType & 0x20) == 0x20)
                sb.AppendLine("\tDevice supports HS-200 mode (SDR 200Mhz) at 1.2V");
            if((csd.DeviceType & 0x40) == 0x40)
                sb.AppendLine("\tDevice supports HS-400 mode (DDR 200Mhz) at 1.8V");
            if((csd.DeviceType & 0x80) == 0x80)
                sb.AppendLine("\tDevice supports HS-400 mode (DDR 200Mhz) at 1.2V");

            sb.AppendFormat("\tCSD version 1.{0} revision 1.{1}", csd.Structure, csd.Revision).AppendLine();

            if((csd.StrobeSupport & 0x01) == 0x01)
            {
                sb.AppendLine("\tDevice supports enhanced strobe mode");
                if((csd.BusWidth & 0x80) == 0x80)
                    sb.AppendLine("\tDevice uses strobe during Data Out, CRC and CMD responses");
                else
                    sb.AppendLine("\tDevice uses strobe during Data Out and CRC responses");
            }

            switch(csd.BusWidth & 0x0F)
            {
                case 0:
                    sb.AppendLine("\tDevice is using 1-bit data bus");
                    break;
                case 1:
                    sb.AppendLine("\tDevice is using 4-bit data bus");
                    break;
                case 2:
                    sb.AppendLine("\tDevice is using 8-bit data bus");
                    break;
                case 5:
                    sb.AppendLine("\tDevice is using 4-bit DDR data bus");
                    break;
                case 6:
                    sb.AppendLine("\tDevice is using 8-bit DDR data bus");
                    break;
                default:
                    sb.AppendFormat("\tDevice is using unknown data bus code {0}", csd.BusWidth & 0x0F).AppendLine();
                    break;
            }

            if((csd.PartitionConfiguration & 0x80) == 0x80)
                sb.AppendLine("\tDevice sends boot acknowledge");

            switch((csd.PartitionConfiguration & 0x38) >> 3)
            {
                case 0:
                    sb.AppendLine("\tDevice is not boot enabled");
                    break;
                case 1:
                    sb.AppendLine("\tDevice boot partition 1 is enabled");
                    break;
                case 2:
                    sb.AppendLine("\tDevice boot partition 2 is enabled");
                    break;
                case 7:
                    sb.AppendLine("\tDevice user area is enable for boot");
                    break;
                default:
                    sb.AppendFormat("\tUnknown enabled boot partition code {0}", (csd.PartitionConfiguration & 0x38) >> 3).AppendLine();
                    break;
            }

            switch(csd.PartitionConfiguration & 0x07)
            {
                case 0:
                    sb.AppendLine("\tThere is no access to boot partition");
                    break;
                case 1:
                    sb.AppendLine("\tThere is read/write access to boot partition 1");
                    break;
                case 2:
                    sb.AppendLine("\tThere is read/write access to boot partition 2");
                    break;
                case 3:
                    sb.AppendLine("\tThere is read/write access to replay protected memory block");
                    break;
                default:
                    sb.AppendFormat("\tThere is access to general purpose partition {0}", (csd.PartitionConfiguration & 0x07) - 3).AppendLine();
                    break;
            }

            if((csd.FirmwareConfiguration & 0x01) == 0x01)
                sb.AppendLine("\tFirmware updates are permanently disabled");

            if(csd.RPMBSize > 0)
                sb.AppendFormat("\tDevice has a {0} KiB replay protected memory block", csd.RPMBSize * 128).AppendLine();

            switch(csd.NativeSectorSize)
            {
                case 0:
                    sb.AppendLine("\tDevice natively uses 512 byte sectors");
                    break;
                case 1:
                    sb.AppendLine("\tDevice natively uses 4096 byte sectors");
                    break;
                default:
                    sb.AppendFormat("\tDevice natively uses unknown sector size indicated by code {0}", csd.NativeSectorSize).AppendLine();
                    break;
            }

            switch(csd.SectorSizeEmulation)
            {
                case 0:
                    sb.AppendLine("\tDevice is emulating 512 byte sectors");
                    break;
                case 1:
                    sb.AppendLine("\tDevice is using natively sized sectors");
                    break;
                default:
                    sb.AppendFormat("\tDevice emulates unknown sector size indicated by code {0}", csd.NativeSectorSize).AppendLine();
                    break;
            }

            switch(csd.SectorSize)
            {
                case 0:
                    sb.AppendLine("\tDevice currently addresses 512 byte sectors");
                    break;
                case 1:
                    sb.AppendLine("\tDevice currently addresses 4096 byte sectors");
                    break;
                default:
                    sb.AppendFormat("\tDevice currently addresses unknown sector size indicated by code {0}", csd.NativeSectorSize).AppendLine();
                    break;
            }

            if((csd.CacheControl & 0x01) == 0x01)
                sb.AppendLine("\tDevice's cache is enabled");

            if((csd.CommandQueueModeEnable & 0x01) == 0x01)
                sb.AppendLine("\tDevice has enabled command queuing");

            return sb.ToString();
        }

        public static string PrettifyExtendedCSD(byte[] response)
        {
            return PrettifyExtendedCSD(DecodeExtendedCSD(response));
        }
    }
}
