| // Copyright (c) 2014 The Chromium OS Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #ifndef POWER_MANAGER_POWERD_SYSTEM_DISPLAY_EXTERNAL_DISPLAY_H_ |
| #define POWER_MANAGER_POWERD_SYSTEM_DISPLAY_EXTERNAL_DISPLAY_H_ |
| |
| #include <stdint.h> |
| |
| #include <memory> |
| #include <queue> |
| #include <string> |
| #include <vector> |
| |
| #include <base/callback.h> |
| #include <base/compiler_specific.h> |
| #include <base/files/file_path.h> |
| #include <base/macros.h> |
| #include <base/time/time.h> |
| #include <base/timer/timer.h> |
| |
| #include "power_manager/common/clock.h" |
| |
| struct i2c_rdwr_ioctl_data; |
| |
| namespace power_manager { |
| namespace system { |
| |
| // Class for controlling an external display via DDC/CI. |
| // |
| // A brief summary: |
| // |
| // DDC/CI is a protocol enabling the host system to read and write various |
| // properties exposed by an external display over the I2C bus. This class is |
| // specifically interested in the display's brightness (a.k.a. luminance) |
| // property. |
| // |
| // DDC/CI allows the current and maximum brightness to be read by sending a |
| // request to the display, waiting for at least 40 milliseconds, and then |
| // reading the reply. It allows the brightness to be modified by sending a |
| // request to the display and then waiting at least 50 milliseconds before |
| // sending any following requests. |
| // |
| // This class caches the current brightness for a brief period of time after |
| // reading it from or writing it to the display; subsequent |
| // AdjustBrightnessByPercent() calls use that cached brightness as a starting |
| // point when computing new brightness levels. If an adjustment is requested |
| // after the cached brightness expires, the brightness is read before the update |
| // level is written. Multiple adjustments are coalesced when possible. |
| // |
| // This class is implemented as a simple state machine. The UpdateState() method |
| // is responsible for transitioning between states. |
| class ExternalDisplay { |
| public: |
| // I2C address to use for DDC/CI. |
| static const uint8_t kDdcI2CAddress; |
| |
| // Address corresponding to the host. |
| static const uint8_t kDdcHostAddress; |
| |
| // Address corresponding to the display. |
| static const uint8_t kDdcDisplayAddress; |
| |
| // "Virtual host address" used as a starting point when checksumming replies |
| // from the display (see DDC/CI v1.1 4.0). |
| static const uint8_t kDdcVirtualHostAddress; |
| |
| // Mask applied to the byte containing the message body length. |
| static const uint8_t kDdcMessageBodyLengthMask; |
| |
| // Opcodes for "Get VCP Feature" requests, "Get VCP Feature" replies, and "Set |
| // VCP Feature" requests, respectively (per DDC/CI v1.1 4.3 and 4.4). |
| static const uint8_t kDdcGetCommand; |
| static const uint8_t kDdcGetReplyCommand; |
| static const uint8_t kDdcSetCommand; |
| |
| // Index of the screen brightness (a.k.a. "luminance") feature. |
| static const uint8_t kDdcBrightnessIndex; |
| |
| // Minimum amount of time to wait after sending a "Set VCP Feature" message |
| // before sending the next message (per DDC/CI v1.1 4.4). |
| static const int kDdcSetDelayMs; |
| |
| // Amount of time to wait after sending a "Get VCP Feature" message before |
| // reading the reply message (per DDC/CI v1.1 4.3). |
| static const int kDdcGetDelayMs; |
| |
| // Amount of time that the brightness value last read from or written to the |
| // display should be honored before a new brightness value is read. |
| static const int kCachedBrightnessValidMs; |
| |
| // Possible outcomes when sending a message to the display. These values are |
| // reported as a histogram and cannot be renumbered. |
| enum class SendResult { |
| // The message was successfully sent to the display. |
| SUCCESS = 0, |
| // The ioctl() syscall failed. |
| IOCTL_FAILED = 1, |
| }; |
| |
| // Possible outcomes when reading a message from the display. These values are |
| // reported as a histogram and cannot be renumbered. |
| enum class ReceiveResult { |
| // The message was successfully read from the display. |
| SUCCESS = 0, |
| // The ioctl() syscall failed. |
| IOCTL_FAILED = 1, |
| // The message had a bad checksum. |
| BAD_CHECKSUM = 2, |
| // The message had an unexpected source address. |
| BAD_ADDRESS = 3, |
| // The message body's length didn't match the expected length. |
| BAD_LENGTH = 4, |
| // The message body contained an unexpected command code. |
| BAD_COMMAND = 5, |
| // The message body contained a non-successful result code. |
| BAD_RESULT = 6, |
| // The message body contained an unexpected feature index. |
| BAD_INDEX = 7, |
| // The message body contained an unexpected maximum value of zero. |
| ZERO_MAX_VALUE = 8, |
| }; |
| |
| // Interface that abstracts the portion of ExternalDisplay that needs to |
| // communicate with devices. |
| class Delegate { |
| public: |
| virtual ~Delegate() {} |
| |
| // Returns a name describing the I2C bus represented by this object. |
| virtual std::string GetName() const = 0; |
| |
| // Performs the I2C operation described by |data| and returns true on |
| // success. |
| virtual bool PerformI2COperation(struct i2c_rdwr_ioctl_data* data) = 0; |
| }; |
| |
| // Real implementation of the Delegate interface. |
| class RealDelegate : public Delegate { |
| public: |
| RealDelegate(); |
| RealDelegate(const RealDelegate&) = delete; |
| RealDelegate& operator=(const RealDelegate&) = delete; |
| |
| ~RealDelegate() override; |
| |
| // Initializes the object to use the I2C device at |i2c_path|. |
| void Init(const base::FilePath& i2c_path); |
| |
| // Delegate implementation: |
| std::string GetName() const override; |
| bool PerformI2COperation(struct i2c_rdwr_ioctl_data* data) override; |
| |
| private: |
| // These values are reported as a histogram and cannot be renumbered. |
| enum class OpenResult { |
| // Calling open() on the I2C device succeeded. |
| SUCCESS = 0, |
| // Calling open() on the I2C device failed with EACCES. |
| FAILURE_EACCES = 1, |
| // Calling open() on the I2C device failed with ENOENT. |
| FAILURE_ENOENT = 2, |
| // Calling open() on the I2C device failed for some other reason. |
| FAILURE_UNKNOWN = 3, |
| }; |
| |
| // Opens |i2c_path_| and updates |fd_|. |
| bool OpenI2CFile(); |
| |
| // Name describing the I2C bus. |
| std::string name_; |
| |
| // File descriptor corresponding to the I2C bus passed to the c'tor. |
| int fd_; |
| |
| // File path for the I2C bus. |
| base::FilePath i2c_path_; |
| |
| }; |
| |
| // Class used by tests to interact with ExternalDisplay's internals. |
| class TestApi { |
| public: |
| explicit TestApi(ExternalDisplay* display); |
| TestApi(const TestApi&) = delete; |
| TestApi& operator=(const TestApi&) = delete; |
| |
| ~TestApi(); |
| |
| // Advances |display_|'s clock by |interval|. |
| void AdvanceTime(base::TimeDelta interval); |
| |
| // Returns the current delay for |display_|'s |timer_|. |
| base::TimeDelta GetTimerDelay() const; |
| |
| // If |display_|'s |timer_| is running, stops it, executes UpdateState(), |
| // and returns true. Otherwise, returns false. |
| bool TriggerTimeout() WARN_UNUSED_RESULT; |
| |
| private: |
| ExternalDisplay* display_; // weak pointer |
| |
| }; |
| |
| explicit ExternalDisplay(std::unique_ptr<Delegate> delegate); |
| ExternalDisplay(const ExternalDisplay&) = delete; |
| ExternalDisplay& operator=(const ExternalDisplay&) = delete; |
| |
| ~ExternalDisplay(); |
| |
| // Adjusts the display's brightness by |offset_percent|, a linearly-calculated |
| // percent in the range [-100.0, 100.0]. Note that the adjustment will happen |
| // asynchronously if the display's current brightness is initially unknown. |
| void AdjustBrightnessByPercent(double offset_percent); |
| |
| private: |
| enum class State { |
| // Not currently mid-request (but if |timer_| is running, temporarily |
| // blocked from sending another request due to a "set brightness" request |
| // having just been sent). |
| IDLE, |
| |
| // Waiting before reading the reply to a "get brightness" request. |
| WAITING_FOR_REPLY, |
| }; |
| |
| // Returns the monitor-specific brightness level corresponding to |percent|, a |
| // percentage in the range [0.0, 100.0]. |
| uint16_t BrightnessPercentToLevel(double percent) const; |
| |
| // Returns true if |current_brightness_percent_| and |max_brightness_level_| |
| // were updated recently enough to be trusted. |
| bool HaveCachedBrightness(); |
| |
| // Returns true if an adjustment (in |pending_brightness_adjustment_percent_|) |
| // is waiting to be applied. |
| bool HavePendingBrightnessAdjustment() const; |
| |
| // Resets |timer_| to run UpdateState() after |delay|. |
| void StartTimer(base::TimeDelta delay); |
| |
| // Sends a message to the display asking it to reply with the current and |
| // maximum brightness. Returns true if the request was sent successfully. |
| bool RequestBrightness(); |
| |
| // Reads a reply from the display containing the current and maximum |
| // brightness (in response to a request sent by RequestBrightness()). Returns |
| // true if the brightness was read successfully. |
| bool ReadBrightness(); |
| |
| // Sends a message to the display asking it to update the current brightness |
| // level (based on |pending_brightness_adjustment_percent_|). Returns true if |
| // the request was sent successfully. |
| bool WriteBrightness(); |
| |
| // Examines the current value of |state_| and performs appropriate actions. |
| void UpdateState(); |
| |
| // Sends a DDC message containing |body| to the display. |
| SendResult SendMessage(const std::vector<uint8_t>& body); |
| |
| // Receives a DDC message from the display, copying its contents to |body|. |
| // |body|'s size determines the expected size of the message body. |
| ReceiveResult ReceiveMessage(std::vector<uint8_t>* body); |
| |
| std::unique_ptr<Delegate> delegate_; |
| Clock clock_; |
| |
| // Current state of the object. |
| State state_; |
| |
| // Brightness believed to be currently used by the display, as a percentage in |
| // the range [0.0, 100.0]. Note that the actual brightness may change in the |
| // background, e.g. in response to the user hitting physical buttons on the |
| // display. |
| double current_brightness_percent_; |
| |
| // Maximum brightness value supported by the display, in display-specific |
| // units. |
| uint16_t max_brightness_level_; |
| |
| // Last time at which |current_brightness_percent_| and |
| // |max_brightness_level_| were updated. |
| base::TimeTicks last_brightness_update_time_; |
| |
| // Amount by which the brightness should be offset, as a percentage in the |
| // range [-100.0, 100.0]. |
| double pending_brightness_adjustment_percent_; |
| |
| // Invokes UpdateState(). Used to enforce the mandatory delays between |
| // requesting the brightness and reading the reply, and after sending a "set" |
| // request to the display. |
| base::OneShotTimer timer_; |
| }; |
| |
| } // namespace system |
| } // namespace power_manager |
| |
| #endif // POWER_MANAGER_POWERD_SYSTEM_DISPLAY_EXTERNAL_DISPLAY_H_ |