commit a6deae8b326864d52fb0e5064f93e3b1575292d1
Author: brogon <brogon@palandor.net>
Date:   Fri Dec 23 00:33:31 2022 +0100

    PCF85063 RTC chip (#3873)
    
    Co-authored-by: Jesse Hills <3060199+jesserockz@users.noreply.github.com>

diff --git a/esphome/components/pcf85063/__init__.py b/esphome/components/pcf85063/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/esphome/components/pcf85063/pcf85063.cpp b/esphome/components/pcf85063/pcf85063.cpp
new file mode 100644
index 0000000..c6a8624
--- /dev/null
+++ b/esphome/components/pcf85063/pcf85063.cpp
@@ -0,0 +1,105 @@
+#include "pcf85063.h"
+#include "esphome/core/log.h"
+
+// Datasheet:
+// - https://datasheets.maximintegrated.com/en/ds/DS1307.pdf
+
+namespace esphome {
+namespace pcf85063 {
+
+static const char *const TAG = "pcf85063";
+
+void PCF85063Component::setup() {
+  ESP_LOGCONFIG(TAG, "Setting up PCF85063...");
+  if (!this->read_rtc_()) {
+    this->mark_failed();
+  }
+}
+
+void PCF85063Component::update() { this->read_time(); }
+
+void PCF85063Component::dump_config() {
+  ESP_LOGCONFIG(TAG, "PCF85063:");
+  LOG_I2C_DEVICE(this);
+  if (this->is_failed()) {
+    ESP_LOGE(TAG, "Communication with PCF85063 failed!");
+  }
+  ESP_LOGCONFIG(TAG, "  Timezone: '%s'", this->timezone_.c_str());
+}
+
+float PCF85063Component::get_setup_priority() const { return setup_priority::DATA; }
+
+void PCF85063Component::read_time() {
+  if (!this->read_rtc_()) {
+    return;
+  }
+  if (pcf85063_.reg.osc_stop) {
+    ESP_LOGW(TAG, "RTC halted, not syncing to system clock.");
+    return;
+  }
+  time::ESPTime rtc_time{.second = uint8_t(pcf85063_.reg.second + 10 * pcf85063_.reg.second_10),
+                         .minute = uint8_t(pcf85063_.reg.minute + 10u * pcf85063_.reg.minute_10),
+                         .hour = uint8_t(pcf85063_.reg.hour + 10u * pcf85063_.reg.hour_10),
+                         .day_of_week = uint8_t(pcf85063_.reg.weekday),
+                         .day_of_month = uint8_t(pcf85063_.reg.day + 10u * pcf85063_.reg.day_10),
+                         .day_of_year = 1,  // ignored by recalc_timestamp_utc(false)
+                         .month = uint8_t(pcf85063_.reg.month + 10u * pcf85063_.reg.month_10),
+                         .year = uint16_t(pcf85063_.reg.year + 10u * pcf85063_.reg.year_10 + 2000)};
+  rtc_time.recalc_timestamp_utc(false);
+  if (!rtc_time.is_valid()) {
+    ESP_LOGE(TAG, "Invalid RTC time, not syncing to system clock.");
+    return;
+  }
+  time::RealTimeClock::synchronize_epoch_(rtc_time.timestamp);
+}
+
+void PCF85063Component::write_time() {
+  auto now = time::RealTimeClock::utcnow();
+  if (!now.is_valid()) {
+    ESP_LOGE(TAG, "Invalid system time, not syncing to RTC.");
+    return;
+  }
+  pcf85063_.reg.year = (now.year - 2000) % 10;
+  pcf85063_.reg.year_10 = (now.year - 2000) / 10 % 10;
+  pcf85063_.reg.month = now.month % 10;
+  pcf85063_.reg.month_10 = now.month / 10;
+  pcf85063_.reg.day = now.day_of_month % 10;
+  pcf85063_.reg.day_10 = now.day_of_month / 10;
+  pcf85063_.reg.weekday = now.day_of_week;
+  pcf85063_.reg.hour = now.hour % 10;
+  pcf85063_.reg.hour_10 = now.hour / 10;
+  pcf85063_.reg.minute = now.minute % 10;
+  pcf85063_.reg.minute_10 = now.minute / 10;
+  pcf85063_.reg.second = now.second % 10;
+  pcf85063_.reg.second_10 = now.second / 10;
+  pcf85063_.reg.osc_stop = false;
+
+  this->write_rtc_();
+}
+
+bool PCF85063Component::read_rtc_() {
+  if (!this->read_bytes(0, this->pcf85063_.raw, sizeof(this->pcf85063_.raw))) {
+    ESP_LOGE(TAG, "Can't read I2C data.");
+    return false;
+  }
+  ESP_LOGD(TAG, "Read  %0u%0u:%0u%0u:%0u%0u 20%0u%0u-%0u%0u-%0u%0u  OSC:%s CLKOUT:%0u", pcf85063_.reg.hour_10,
+           pcf85063_.reg.hour, pcf85063_.reg.minute_10, pcf85063_.reg.minute, pcf85063_.reg.second_10,
+           pcf85063_.reg.second, pcf85063_.reg.year_10, pcf85063_.reg.year, pcf85063_.reg.month_10, pcf85063_.reg.month,
+           pcf85063_.reg.day_10, pcf85063_.reg.day, ONOFF(!pcf85063_.reg.osc_stop), pcf85063_.reg.clkout_control);
+
+  return true;
+}
+
+bool PCF85063Component::write_rtc_() {
+  if (!this->write_bytes(0, this->pcf85063_.raw, sizeof(this->pcf85063_.raw))) {
+    ESP_LOGE(TAG, "Can't write I2C data.");
+    return false;
+  }
+  ESP_LOGD(TAG, "Write %0u%0u:%0u%0u:%0u%0u 20%0u%0u-%0u%0u-%0u%0u  OSC:%s CLKOUT:%0u", pcf85063_.reg.hour_10,
+           pcf85063_.reg.hour, pcf85063_.reg.minute_10, pcf85063_.reg.minute, pcf85063_.reg.second_10,
+           pcf85063_.reg.second, pcf85063_.reg.year_10, pcf85063_.reg.year, pcf85063_.reg.month_10, pcf85063_.reg.month,
+           pcf85063_.reg.day_10, pcf85063_.reg.day, ONOFF(!pcf85063_.reg.osc_stop), pcf85063_.reg.clkout_control);
+  return true;
+}
+}  // namespace pcf85063
+}  // namespace esphome
diff --git a/esphome/components/pcf85063/pcf85063.h b/esphome/components/pcf85063/pcf85063.h
new file mode 100644
index 0000000..1a3fd70
--- /dev/null
+++ b/esphome/components/pcf85063/pcf85063.h
@@ -0,0 +1,96 @@
+#pragma once
+
+#include "esphome/core/component.h"
+#include "esphome/components/i2c/i2c.h"
+#include "esphome/components/time/real_time_clock.h"
+
+namespace esphome {
+namespace pcf85063 {
+
+class PCF85063Component : public time::RealTimeClock, public i2c::I2CDevice {
+ public:
+  void setup() override;
+  void update() override;
+  void dump_config() override;
+  float get_setup_priority() const override;
+  void read_time();
+  void write_time();
+
+ protected:
+  bool read_rtc_();
+  bool write_rtc_();
+  union PCF85063Reg {
+    struct {
+      // Control_1 register
+      bool cap_12pf : 1;
+      bool am_pm : 1;
+      bool correction_int_enable : 1;
+      bool : 1;
+      bool soft_reset : 1;
+      bool stop : 1;
+      bool : 1;
+      bool ext_test : 1;
+
+      // Control_2 register
+      uint8_t clkout_control : 3;
+      bool timer_flag : 1;
+      bool halfminute_int : 1;
+      bool minute_int : 1;
+      bool alarm_flag : 1;
+      bool alarm_int : 1;
+
+      // Offset register
+      uint8_t offset : 7;
+      bool coarse_mode : 1;
+
+      // nvRAM register
+      uint8_t nvram : 8;
+
+      // Seconds register
+      uint8_t second : 4;
+      uint8_t second_10 : 3;
+      bool osc_stop : 1;
+
+      // Minutes register
+      uint8_t minute : 4;
+      uint8_t minute_10 : 3;
+      uint8_t : 1;
+
+      // Hours register
+      uint8_t hour : 4;
+      uint8_t hour_10 : 2;
+      uint8_t : 2;
+
+      // Days register
+      uint8_t day : 4;
+      uint8_t day_10 : 2;
+      uint8_t : 2;
+
+      // Weekdays register
+      uint8_t weekday : 3;
+      uint8_t unused_3 : 5;
+
+      // Months register
+      uint8_t month : 4;
+      uint8_t month_10 : 1;
+      uint8_t : 3;
+
+      // Years register
+      uint8_t year : 4;
+      uint8_t year_10 : 4;
+    } reg;
+    mutable uint8_t raw[sizeof(reg)];
+  } pcf85063_;
+};
+
+template<typename... Ts> class WriteAction : public Action<Ts...>, public Parented<PCF85063Component> {
+ public:
+  void play(Ts... x) override { this->parent_->write_time(); }
+};
+
+template<typename... Ts> class ReadAction : public Action<Ts...>, public Parented<PCF85063Component> {
+ public:
+  void play(Ts... x) override { this->parent_->read_time(); }
+};
+}  // namespace pcf85063
+}  // namespace esphome
diff --git a/esphome/components/pcf85063/time.py b/esphome/components/pcf85063/time.py
new file mode 100644
index 0000000..67ec230
--- /dev/null
+++ b/esphome/components/pcf85063/time.py
@@ -0,0 +1,60 @@
+import esphome.config_validation as cv
+import esphome.codegen as cg
+from esphome import automation
+from esphome.components import i2c, time
+from esphome.const import CONF_ID
+
+
+CODEOWNERS = ["@brogon"]
+DEPENDENCIES = ["i2c"]
+pcf85063_ns = cg.esphome_ns.namespace("pcf85063")
+PCF85063Component = pcf85063_ns.class_(
+    "PCF85063Component", time.RealTimeClock, i2c.I2CDevice
+)
+WriteAction = pcf85063_ns.class_("WriteAction", automation.Action)
+ReadAction = pcf85063_ns.class_("ReadAction", automation.Action)
+
+
+CONFIG_SCHEMA = time.TIME_SCHEMA.extend(
+    {
+        cv.GenerateID(): cv.declare_id(PCF85063Component),
+    }
+).extend(i2c.i2c_device_schema(0x51))
+
+
+@automation.register_action(
+    "pcf85063.write_time",
+    WriteAction,
+    cv.Schema(
+        {
+            cv.GenerateID(): cv.use_id(PCF85063Component),
+        }
+    ),
+)
+async def pcf85063_write_time_to_code(config, action_id, template_arg, args):
+    var = cg.new_Pvariable(action_id, template_arg)
+    await cg.register_parented(var, config[CONF_ID])
+    return var
+
+
+@automation.register_action(
+    "pcf85063.read_time",
+    ReadAction,
+    automation.maybe_simple_id(
+        {
+            cv.GenerateID(): cv.use_id(PCF85063Component),
+        }
+    ),
+)
+async def pcf85063_read_time_to_code(config, action_id, template_arg, args):
+    var = cg.new_Pvariable(action_id, template_arg)
+    await cg.register_parented(var, config[CONF_ID])
+    return var
+
+
+async def to_code(config):
+    var = cg.new_Pvariable(config[CONF_ID])
+
+    await cg.register_component(var, config)
+    await i2c.register_i2c_device(var, config)
+    await time.register_time(var, config)